mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Basic hover to select metric
This commit is contained in:
@@ -12,6 +12,13 @@ import AppStore from '../stores/app-store';
|
||||
|
||||
const log = debug('scope:app-actions');
|
||||
|
||||
export function selectMetric(metricId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.SELECT_METRIC,
|
||||
metricId: metricId
|
||||
});
|
||||
}
|
||||
|
||||
export function changeTopologyOption(option, value, topologyId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
|
||||
|
||||
export default function NodeShapeCircle({highlighted, size, color, metrics}) {
|
||||
export default function NodeShapeCircle({highlighted, size, color, metric}) {
|
||||
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, vp} = getMetricValue(metrics, size);
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -24,7 +24,7 @@ export default function NodeShapeCircle({highlighted, size, color, metrics}) {
|
||||
<circle r={size * 0.45} className="metric-fill" clipPath={`url(#${clipId})`} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(vp)}
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -15,7 +15,7 @@ function polygon(r, sides) {
|
||||
return points;
|
||||
}
|
||||
|
||||
export default function NodeShapeHeptagon({highlighted, size, color, metrics}) {
|
||||
export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
|
||||
const scaledSize = size * 1.0;
|
||||
const pathProps = v => ({
|
||||
d: line(polygon(scaledSize * v, 7)),
|
||||
@@ -23,7 +23,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metrics}) {
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, vp} = getMetricValue(metrics, size);
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -43,7 +43,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metrics}) {
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(0.45)} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(vp)}
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -25,7 +25,7 @@ function getPoints(h) {
|
||||
}
|
||||
|
||||
|
||||
export default function NodeShapeHex({highlighted, size, color, metrics}) {
|
||||
export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
const pathProps = v => ({
|
||||
d: getPoints(size * v * 2),
|
||||
transform: `rotate(90) translate(-${size * getWidth(v)}, -${size * v})`
|
||||
@@ -35,7 +35,7 @@ export default function NodeShapeHex({highlighted, size, color, metrics}) {
|
||||
const upperHexBitHeight = -0.25 * size * shadowSize;
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, vp} = getMetricValue(metrics, size);
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -54,7 +54,7 @@ export default function NodeShapeHex({highlighted, size, color, metrics}) {
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(vp)}
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import {formatCanvasMetric, getMetricValue} from '../utils/data-utils.js';
|
||||
|
||||
export default function NodeShapeSquare({
|
||||
highlighted, size, color, rx = 0, ry = 0, metrics
|
||||
highlighted, size, color, rx = 0, ry = 0, metric
|
||||
}) {
|
||||
const rectProps = v => ({
|
||||
width: v * size * 2,
|
||||
@@ -14,7 +14,7 @@ export default function NodeShapeSquare({
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, vp} = getMetricValue(metrics, size);
|
||||
const {height, v} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -34,7 +34,7 @@ export default function NodeShapeSquare({
|
||||
<rect className="metric-fill" clipPath={`url(#${clipId})`} {...rectProps(0.45)} />
|
||||
{highlighted ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formatCanvasMetric(vp)}
|
||||
{formatCanvasMetric(v)}
|
||||
</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -151,7 +151,7 @@ export default class NodesChart extends React.Component {
|
||||
pseudo={node.get('pseudo')}
|
||||
nodeCount={node.get('nodeCount')}
|
||||
subLabel={node.get('subLabel')}
|
||||
metrics={node.get('metrics')}
|
||||
metric={node.getIn(['metrics', this.props.selectedMetric])}
|
||||
rank={node.get('rank')}
|
||||
selectedNodeScale={selectedNodeScale}
|
||||
nodeScale={nodeScale}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { getApiDetails, getTopologies } from '../utils/web-api-utils';
|
||||
import { hitEsc } from '../actions/app-actions';
|
||||
import Details from './details';
|
||||
import Nodes from './nodes';
|
||||
import MetricSelector from './metric-selector';
|
||||
import EmbeddedTerminal from './embedded-terminal';
|
||||
import { getRouter } from '../utils/router-utils';
|
||||
import { showingDebugToolbar, DebugToolbar } from './debug-toolbar.js';
|
||||
@@ -33,6 +34,7 @@ function getStateFromStores() {
|
||||
nodeDetails: AppStore.getNodeDetails(),
|
||||
nodes: AppStore.getNodes(),
|
||||
selectedNodeId: AppStore.getSelectedNodeId(),
|
||||
selectedMetric: AppStore.getSelectedMetric(),
|
||||
topologies: AppStore.getTopologies(),
|
||||
topologiesLoaded: AppStore.isTopologiesLoaded(),
|
||||
updatePaused: AppStore.isUpdatePaused(),
|
||||
@@ -103,14 +105,20 @@ export default class App extends React.Component {
|
||||
currentTopology={this.state.currentTopology} />
|
||||
</div>
|
||||
|
||||
<Nodes nodes={this.state.nodes} highlightedNodeIds={this.state.highlightedNodeIds}
|
||||
highlightedEdgeIds={this.state.highlightedEdgeIds} detailsWidth={detailsWidth}
|
||||
selectedNodeId={this.state.selectedNodeId} topMargin={topMargin}
|
||||
<Nodes
|
||||
nodes={this.state.nodes}
|
||||
highlightedNodeIds={this.state.highlightedNodeIds}
|
||||
highlightedEdgeIds={this.state.highlightedEdgeIds}
|
||||
detailsWidth={detailsWidth}
|
||||
selectedNodeId={this.state.selectedNodeId}
|
||||
topMargin={topMargin}
|
||||
selectedMetric={this.state.selectedMetric}
|
||||
forceRelayout={this.state.forceRelayout}
|
||||
topologyOptions={this.state.activeTopologyOptions}
|
||||
topologyId={this.state.currentTopologyId} />
|
||||
|
||||
<Sidebar>
|
||||
<MetricSelector selectedMetric={this.state.selectedMetric}/>
|
||||
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
|
||||
topologiesLoaded={this.state.topologiesLoaded}
|
||||
websocketClosed={this.state.websocketClosed} />
|
||||
|
||||
36
client/app/scripts/components/metric-selector.js
Normal file
36
client/app/scripts/components/metric-selector.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { selectMetric } from '../actions/app-actions';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const METRICS = {
|
||||
'CPU': 'process_cpu_usage_percent',
|
||||
'Memory': 'process_memory_usage_bytes',
|
||||
'Open Files': 'open_files_count'
|
||||
};
|
||||
|
||||
// docker_cpu_total_usage
|
||||
// docker_memory_usage
|
||||
|
||||
function onMouseOver(k) {
|
||||
return selectMetric(k);
|
||||
}
|
||||
|
||||
export default function MetricSelector({selectedMetric}) {
|
||||
return (
|
||||
<div className="available-metrics">
|
||||
{_.map(METRICS, (key, name) => {
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
className={classNames('sidebar-item', {
|
||||
'selected': (key === selectedMetric)
|
||||
})}
|
||||
onMouseOver={() => onMouseOver(key)}>
|
||||
{name}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -33,7 +33,8 @@ const ACTION_TYPES = [
|
||||
'RECEIVE_TOPOLOGIES',
|
||||
'RECEIVE_API_DETAILS',
|
||||
'RECEIVE_ERROR',
|
||||
'ROUTE_TOPOLOGY'
|
||||
'ROUTE_TOPOLOGY',
|
||||
'SELECT_METRIC'
|
||||
];
|
||||
|
||||
export default _.zipObject(ACTION_TYPES, ACTION_TYPES);
|
||||
|
||||
@@ -58,6 +58,7 @@ let routeSet = false;
|
||||
let controlPipes = makeOrderedMap(); // pipeId -> controlPipe
|
||||
let updatePausedAt = null; // Date
|
||||
let websocketClosed = true;
|
||||
let selectedMetric = 'process_cpu_usage_percent';
|
||||
|
||||
const topologySorter = topology => topology.get('rank');
|
||||
|
||||
@@ -164,6 +165,10 @@ export class AppStore extends Store {
|
||||
return adjacentNodes;
|
||||
}
|
||||
|
||||
getSelectedMetric() {
|
||||
return selectedMetric;
|
||||
}
|
||||
|
||||
getControlStatus() {
|
||||
return controlStatus.toJS();
|
||||
}
|
||||
@@ -401,6 +406,11 @@ export class AppStore extends Store {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ActionTypes.SELECT_METRIC: {
|
||||
selectedMetric = payload.metricId;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DESELECT_NODE: {
|
||||
closeNodeDetails();
|
||||
this.__emitChange();
|
||||
|
||||
@@ -78,19 +78,19 @@ export function addMetrics(delta, prevNodes) {
|
||||
}
|
||||
|
||||
|
||||
export function getMetricValue(metrics, size) {
|
||||
if (!metrics) {
|
||||
return {height: 0, vp: null};
|
||||
export function getMetricValue(metric, size) {
|
||||
if (!metric) {
|
||||
return {height: 0, v: null};
|
||||
}
|
||||
|
||||
const max = 100;
|
||||
const v = metrics.getIn(['process_cpu_usage_percent', 'samples', 0, 'value']);
|
||||
const max = metric.getIn(['max']);
|
||||
const v = metric.getIn(['samples', 0, 'value']);
|
||||
const vp = v === 0 ? 0 : v / max;
|
||||
const baseline = 0.00;
|
||||
const baseline = 0.05;
|
||||
const displayedValue = vp * (1 - baseline) + baseline;
|
||||
const height = size * displayedValue;
|
||||
|
||||
return {height, vp};
|
||||
return {height, v};
|
||||
}
|
||||
|
||||
const formatLargeValue = d3.format('s');
|
||||
@@ -98,5 +98,5 @@ export function formatCanvasMetric(v) {
|
||||
if (v === null) {
|
||||
return 'n/a';
|
||||
}
|
||||
return formatLargeValue(Number(v * 100).toFixed(1)) + '%';
|
||||
return formatLargeValue(Number(v).toFixed(1));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user