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:
Simon Howe
2016-03-09 14:31:02 +01:00
parent cc3d392010
commit 319fe31356
11 changed files with 114 additions and 32 deletions

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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> :

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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>
);
}

View File

@@ -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>
);
})}

View File

@@ -24,6 +24,7 @@ const ACTION_TYPES = [
'LEAVE_EDGE',
'LEAVE_NODE',
'LOCK_METRIC',
'UNLOCK_METRIC',
'OPEN_WEBSOCKET',
'RECEIVE_CONTROL_PIPE',
'RECEIVE_CONTROL_PIPE_STATUS',

View File

@@ -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;
}

View File

@@ -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;
}
}