mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
Show metrics that are available for displayed nodes.
- change color to bg - show "x" to remove the metric. - Small debugToolbar enhancements.
This commit is contained in:
@@ -38,6 +38,12 @@ export function lockMetric(metricId) {
|
||||
});
|
||||
}
|
||||
|
||||
export function unlockMetric() {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.UNLOCK_METRIC,
|
||||
});
|
||||
}
|
||||
|
||||
export function changeTopologyOption(option, value, topologyId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.CHANGE_TOPOLOGY_OPTION,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {getMetricValue} from '../utils/data-utils.js';
|
||||
export default function NodeShapeCircle({highlighted, size, color, metric}) {
|
||||
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -22,7 +22,7 @@ export default function NodeShapeCircle({highlighted, size, color, metric}) {
|
||||
<circle r={size * 0.5} className="border" stroke={color} />
|
||||
<circle r={size * 0.45} className="shadow" />
|
||||
<circle r={size * 0.45} className="metric-fill" clipPath={`url(#${clipId})`} />
|
||||
{highlighted ?
|
||||
{highlighted && value !== null ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -23,7 +23,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -41,7 +41,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) {
|
||||
<path className="border" stroke={color} {...pathProps(0.5)} />
|
||||
<path className="shadow" {...pathProps(0.45)} />
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(0.45)} />
|
||||
{highlighted ?
|
||||
{highlighted && value !== null ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
const upperHexBitHeight = -0.25 * size * shadowSize;
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -52,7 +52,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) {
|
||||
<path className="border" stroke={color} {...pathProps(0.5)} />
|
||||
<path className="shadow" {...pathProps(shadowSize)} />
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />
|
||||
{highlighted ?
|
||||
{highlighted && value !== null ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>
|
||||
{formattedValue}
|
||||
</text> :
|
||||
|
||||
@@ -14,7 +14,7 @@ export default function NodeShapeSquare({
|
||||
});
|
||||
|
||||
const clipId = `mask-${Math.random()}`;
|
||||
const {height, formattedValue} = getMetricValue(metric, size);
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
return (
|
||||
<g className="shape">
|
||||
@@ -32,7 +32,7 @@ export default function NodeShapeSquare({
|
||||
<rect className="border" stroke={color} {...rectProps(0.5)} />
|
||||
<rect className="shadow" {...rectProps(0.45)} />
|
||||
<rect className="metric-fill" clipPath={`url(#${clipId})`} {...rectProps(0.45)} />
|
||||
{highlighted ?
|
||||
{highlighted && value !== null ?
|
||||
<text dy="0.35em" style={{'textAnchor': 'middle'}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -14,9 +14,11 @@ 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';
|
||||
import { showingDebugToolbar, toggleDebugToolbar,
|
||||
DebugToolbar } from './debug-toolbar.js';
|
||||
|
||||
const ESC_KEY_CODE = 27;
|
||||
const D_KEY_CODE = 68;
|
||||
const RIGHT_ANGLE_KEY_IDENTIFIER = 'U+003C';
|
||||
const LEFT_ANGLE_KEY_IDENTIFIER = 'U+003E';
|
||||
|
||||
@@ -80,6 +82,9 @@ export default class App extends React.Component {
|
||||
lockNextMetric(-1);
|
||||
} else if (ev.keyIdentifier === LEFT_ANGLE_KEY_IDENTIFIER) {
|
||||
lockNextMetric(1);
|
||||
} else if (ev.keyCode === D_KEY_CODE) {
|
||||
toggleDebugToolbar();
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,14 @@ const log = debug('scope:debug-panel');
|
||||
import { receiveNodesDelta } from '../actions/app-actions';
|
||||
import AppStore from '../stores/app-store';
|
||||
|
||||
|
||||
const SHAPES = ['square', 'hexagon', 'heptagon', 'circle'];
|
||||
const NODE_COUNTS = [1, 2, 3];
|
||||
const STACK_VARIANTS = [false, true];
|
||||
|
||||
const sample = (collection) => _.range(_.random(4)).map(() => _.sample(collection));
|
||||
|
||||
|
||||
const shapeTypes = {
|
||||
square: ['Process', 'Processes'],
|
||||
hexagon: ['Container', 'Containers'],
|
||||
@@ -21,6 +23,7 @@ const shapeTypes = {
|
||||
circle: ['Host', 'Hosts']
|
||||
};
|
||||
|
||||
|
||||
const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1).map(n => String.fromCharCode(n));
|
||||
|
||||
const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) => ({
|
||||
@@ -38,11 +41,13 @@ const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCou
|
||||
rank: 'alpine'
|
||||
});
|
||||
|
||||
|
||||
function label(shape, stacked) {
|
||||
const type = shapeTypes[shape];
|
||||
return stacked ? `Group of ${type[1]}` : type[0];
|
||||
}
|
||||
|
||||
|
||||
function addAllVariants() {
|
||||
const newNodes = _.flattenDeep(STACK_VARIANTS.map(stack => {
|
||||
return SHAPES.map(s => {
|
||||
@@ -58,6 +63,7 @@ function addAllVariants() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function addNodes(n) {
|
||||
const ns = AppStore.getNodes();
|
||||
const nodeNames = ns.keySeq().toJS();
|
||||
@@ -78,8 +84,27 @@ function addNodes(n) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
export function showingDebugToolbar() {
|
||||
return Boolean(localStorage.debugToolbar);
|
||||
return 'debugToolbar' in localStorage && JSON.parse(localStorage.debugToolbar);
|
||||
}
|
||||
|
||||
|
||||
export function toggleDebugToolbar() {
|
||||
if ('debugToolbar' in localStorage) {
|
||||
localStorage.debugToolbar = !showingDebugToolbar();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function enableLog(ns) {
|
||||
debug.enable(`scope:${ns}`);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function disableLog() {
|
||||
debug.disable();
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
export class DebugToolbar extends React.Component {
|
||||
@@ -101,12 +126,21 @@ export class DebugToolbar extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="debug-panel">
|
||||
<label>Add nodes </label>
|
||||
<button onClick={() => addNodes(1)}>+1</button>
|
||||
<button onClick={() => addNodes(10)}>+10</button>
|
||||
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
|
||||
<button onClick={() => addNodes(this.state.nodesToAdd)}>+</button>
|
||||
<button onClick={() => addAllVariants()}>Variants</button>
|
||||
<div>
|
||||
<label>Add nodes </label>
|
||||
<button onClick={() => addNodes(1)}>+1</button>
|
||||
<button onClick={() => addNodes(10)}>+10</button>
|
||||
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
|
||||
<button onClick={() => addNodes(this.state.nodesToAdd)}>+</button>
|
||||
<button onClick={() => addAllVariants()}>Variants</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Logging</label>
|
||||
<button onClick={() => enableLog('*')}>scope:*</button>
|
||||
<button onClick={() => enableLog('dispatcher')}>scope:dispatcher</button>
|
||||
<button onClick={() => disableLog()}>Disable log</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
import React from 'react';
|
||||
import { selectMetric, lockMetric } from '../actions/app-actions';
|
||||
import { selectMetric, lockMetric, unlockMetric } from '../actions/app-actions';
|
||||
import classNames from 'classnames';
|
||||
|
||||
const CROSS = '\u274C';
|
||||
// const MINUS = '\u2212';
|
||||
// const DOT = '\u2022';
|
||||
|
||||
// docker_cpu_total_usage
|
||||
// docker_memory_usage
|
||||
|
||||
function onMouseOver(k) {
|
||||
return selectMetric(k);
|
||||
selectMetric(k);
|
||||
}
|
||||
|
||||
function onMouseClick(k) {
|
||||
return lockMetric(k);
|
||||
function onMouseClick(k, lockedMetric) {
|
||||
if (k === lockedMetric) {
|
||||
unlockMetric(k);
|
||||
} else {
|
||||
lockMetric(k);
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseOut(k) {
|
||||
@@ -23,16 +31,25 @@ export default function MetricSelector({availableCanvasMetrics, selectedMetric,
|
||||
className="available-metrics"
|
||||
onMouseLeave={() => onMouseOut(lockedMetric)}>
|
||||
{availableCanvasMetrics.map(({id, label}) => {
|
||||
const isLocked = (id === lockedMetric);
|
||||
const isSelected = (id === selectedMetric);
|
||||
const className = classNames('sidebar-item', {
|
||||
'locked': isLocked,
|
||||
'selected': isSelected
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
key={id}
|
||||
className={classNames('sidebar-item', {
|
||||
'locked': (id === lockedMetric),
|
||||
'selected': (id === selectedMetric)
|
||||
})}
|
||||
className={className}
|
||||
onMouseOver={() => onMouseOver(id)}
|
||||
onClick={() => onMouseClick(id)}>
|
||||
onClick={() => onMouseClick(id, lockedMetric)}>
|
||||
{label}
|
||||
{isLocked && <span className="sidebar-item-actions">
|
||||
<span className="sidebar-item-action">
|
||||
{CROSS}
|
||||
</span>
|
||||
</span>}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -24,6 +24,7 @@ const ACTION_TYPES = [
|
||||
'LEAVE_EDGE',
|
||||
'LEAVE_NODE',
|
||||
'LOCK_METRIC',
|
||||
'UNLOCK_METRIC',
|
||||
'OPEN_WEBSOCKET',
|
||||
'RECEIVE_CONTROL_PIPE',
|
||||
'RECEIVE_CONTROL_PIPE_STATUS',
|
||||
|
||||
@@ -61,11 +61,7 @@ let websocketClosed = true;
|
||||
|
||||
let selectedMetric = 'process_cpu_usage_percent';
|
||||
let lockedMetric = selectedMetric;
|
||||
const availableCanvasMetrics = [
|
||||
{label: 'CPU', id: 'process_cpu_usage_percent'},
|
||||
{label: 'Memory', id: 'process_memory_usage_bytes'},
|
||||
{label: 'Open Files', id: 'open_files_count'}
|
||||
];
|
||||
let availableCanvasMetrics = [];
|
||||
|
||||
|
||||
const topologySorter = topology => topology.get('rank');
|
||||
@@ -402,6 +398,7 @@ export class AppStore extends Store {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
availableCanvasMetrics = [];
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
@@ -412,6 +409,7 @@ export class AppStore extends Store {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
availableCanvasMetrics = [];
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
@@ -433,6 +431,11 @@ export class AppStore extends Store {
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.UNLOCK_METRIC: {
|
||||
lockedMetric = null;
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
case ActionTypes.DESELECT_NODE: {
|
||||
closeNodeDetails();
|
||||
this.__emitChange();
|
||||
@@ -623,6 +626,16 @@ export class AppStore extends Store {
|
||||
setDefaultTopologyOptions(topologies);
|
||||
}
|
||||
topologiesLoaded = true;
|
||||
|
||||
availableCanvasMetrics = nodes
|
||||
.valueSeq()
|
||||
.flatMap(n => (n.get('metrics') || makeMap()).keys())
|
||||
.toSet()
|
||||
.sort()
|
||||
.toJS()
|
||||
.map(v => {
|
||||
return {id: v, label: v};
|
||||
});
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -409,7 +409,7 @@ h2 {
|
||||
|
||||
.metric-fill {
|
||||
stroke: none;
|
||||
fill: yellowgreen;
|
||||
fill: @background-darker-color;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
@@ -1054,9 +1054,15 @@ h2 {
|
||||
.debug-panel {
|
||||
.shadow-2;
|
||||
background-color: #fff;
|
||||
top: 10px;
|
||||
top: 80px;
|
||||
position: absolute;
|
||||
padding: 10px;
|
||||
left: 10px;
|
||||
z-index: 10000;
|
||||
|
||||
opacity: 0.3;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user