diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 272d7088d..9f8e34608 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -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, diff --git a/client/app/scripts/charts/node-shape-circle.js b/client/app/scripts/charts/node-shape-circle.js index 6f26c9173..176af9d59 100644 --- a/client/app/scripts/charts/node-shape-circle.js +++ b/client/app/scripts/charts/node-shape-circle.js @@ -4,7 +4,7 @@ import {getMetricValue} from '../utils/data-utils.js'; export default function NodeShapeCircle({highlighted, size, color, metric}) { const hightlightNode = ; const clipId = `mask-${Math.random()}`; - const {height, formattedValue} = getMetricValue(metric, size); + const {height, value, formattedValue} = getMetricValue(metric, size); return ( @@ -22,7 +22,7 @@ export default function NodeShapeCircle({highlighted, size, color, metric}) { - {highlighted ? + {highlighted && value !== null ? {formattedValue} : } diff --git a/client/app/scripts/charts/node-shape-heptagon.js b/client/app/scripts/charts/node-shape-heptagon.js index 246b22032..68eb1274d 100644 --- a/client/app/scripts/charts/node-shape-heptagon.js +++ b/client/app/scripts/charts/node-shape-heptagon.js @@ -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 ( @@ -41,7 +41,7 @@ export default function NodeShapeHeptagon({highlighted, size, color, metric}) { - {highlighted ? + {highlighted && value !== null ? {formattedValue} : } diff --git a/client/app/scripts/charts/node-shape-hex.js b/client/app/scripts/charts/node-shape-hex.js index 479ff5dbb..c1b6ffc4e 100644 --- a/client/app/scripts/charts/node-shape-hex.js +++ b/client/app/scripts/charts/node-shape-hex.js @@ -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 ( @@ -52,7 +52,7 @@ export default function NodeShapeHex({highlighted, size, color, metric}) { - {highlighted ? + {highlighted && value !== null ? {formattedValue} : diff --git a/client/app/scripts/charts/node-shape-square.js b/client/app/scripts/charts/node-shape-square.js index 48bb101ab..e2d12f5bc 100644 --- a/client/app/scripts/charts/node-shape-square.js +++ b/client/app/scripts/charts/node-shape-square.js @@ -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 ( @@ -32,7 +32,7 @@ export default function NodeShapeSquare({ - {highlighted ? + {highlighted && value !== null ? {formattedValue} : } diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 8fc19e087..74435858e 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -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(); } } diff --git a/client/app/scripts/components/debug-toolbar.js b/client/app/scripts/components/debug-toolbar.js index 5c880053a..60bd30c2d 100644 --- a/client/app/scripts/components/debug-toolbar.js +++ b/client/app/scripts/components/debug-toolbar.js @@ -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 (
- - - - - - +
+ + + + + + +
+ +
+ + + + +
); } diff --git a/client/app/scripts/components/metric-selector.js b/client/app/scripts/components/metric-selector.js index 89761daa0..cb5d4255f 100644 --- a/client/app/scripts/components/metric-selector.js +++ b/client/app/scripts/components/metric-selector.js @@ -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 (
onMouseOver(id)} - onClick={() => onMouseClick(id)}> + onClick={() => onMouseClick(id, lockedMetric)}> {label} + {isLocked && + + {CROSS} + + }
); })} diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index ab5a20ca2..151143483 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -24,6 +24,7 @@ const ACTION_TYPES = [ 'LEAVE_EDGE', 'LEAVE_NODE', 'LOCK_METRIC', + 'UNLOCK_METRIC', 'OPEN_WEBSOCKET', 'RECEIVE_CONTROL_PIPE', 'RECEIVE_CONTROL_PIPE_STATUS', diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js index 37d66125c..ca61ad508 100644 --- a/client/app/scripts/stores/app-store.js +++ b/client/app/scripts/stores/app-store.js @@ -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; } diff --git a/client/app/styles/main.less b/client/app/styles/main.less index e9d80b75b..8e1a0cf36 100644 --- a/client/app/styles/main.less +++ b/client/app/styles/main.less @@ -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; + } }