mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
metrics-on-canvas review feedback updates.
- Refactor some things. - Fixes heptagon moc rendering - Experiment w/ duller colors.
This commit is contained in:
@@ -21,14 +21,14 @@ export function selectMetric(metricId) {
|
||||
}
|
||||
|
||||
export function pinNextMetric(delta) {
|
||||
const metrics = AppStore.getAvailableCanvasMetrics().map(m => m.id);
|
||||
const metrics = AppStore.getAvailableCanvasMetrics().map(m => m.get('id'));
|
||||
const currentIndex = metrics.indexOf(AppStore.getSelectedMetric());
|
||||
const nextMetric = metrics[modulo(currentIndex + delta, metrics.length)];
|
||||
const nextIndex = modulo(currentIndex + delta, metrics.count());
|
||||
const nextMetric = metrics.get(nextIndex);
|
||||
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.PIN_METRIC,
|
||||
metricId: nextMetric,
|
||||
metricType: AppStore.getAvailableCanvasMetricsTypes()[nextMetric]
|
||||
});
|
||||
updateRoute();
|
||||
}
|
||||
@@ -37,7 +37,6 @@ export function pinMetric(metricId) {
|
||||
AppDispatcher.dispatch({
|
||||
type: ActionTypes.PIN_METRIC,
|
||||
metricId,
|
||||
metricType: AppStore.getAvailableCanvasMetricsTypes()[metricId]
|
||||
});
|
||||
updateRoute();
|
||||
}
|
||||
|
||||
@@ -1,39 +1,25 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
export default function NodeShapeCircle({id, highlighted, size, color, metric}) {
|
||||
const hightlightNode = <circle r={size * 0.7} className="highlighted" />;
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const metricStyle = {
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
export default function NodeShapeCircle({id, highlighted, size, color, metric}) {
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, hasMetric, formattedValue} = getMetricValue(metric, size);
|
||||
const metricStyle = { fill: getMetricColor(metric) };
|
||||
const className = classNames('shape', { metrics: hasMetric });
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
|
||||
return (
|
||||
<g className={className}>
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<rect
|
||||
width={size}
|
||||
height={size}
|
||||
x={-size * 0.5}
|
||||
y={size * 0.5 - height}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
{highlighted && hightlightNode}
|
||||
{hasMetric && getClipPathDefinition(clipId, size, height)}
|
||||
{highlighted && <circle r={size * 0.7} className="highlighted" />}
|
||||
<circle r={size * 0.5} className="border" stroke={color} />
|
||||
<circle r={size * 0.45} className="shadow" />
|
||||
<circle r={size * 0.45} className="metric-fill" style={metricStyle}
|
||||
clipPath={`url(#${clipId})`} />
|
||||
{highlighted && value !== null ?
|
||||
{hasMetric && <circle r={size * 0.45} className="metric-fill" style={metricStyle}
|
||||
clipPath={`url(#${clipId})`} />}
|
||||
{highlighted && hasMetric ?
|
||||
<text style={{fontSize}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
|
||||
const line = d3.svg.line()
|
||||
.interpolate('cardinal-closed')
|
||||
.tension(0.25);
|
||||
|
||||
|
||||
function polygon(r, sides) {
|
||||
const a = (Math.PI * 2) / sides;
|
||||
const points = [[r, 0]];
|
||||
@@ -17,6 +19,7 @@ function polygon(r, sides) {
|
||||
return points;
|
||||
}
|
||||
|
||||
|
||||
export default function NodeShapeHeptagon({id, highlighted, size, color, metric}) {
|
||||
const scaledSize = size * 1.0;
|
||||
const pathProps = v => ({
|
||||
@@ -25,34 +28,20 @@ export default function NodeShapeHeptagon({id, highlighted, size, color, metric}
|
||||
});
|
||||
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const {height, hasMetric, formattedValue} = getMetricValue(metric, size);
|
||||
const metricStyle = { fill: getMetricColor(metric) };
|
||||
const className = classNames('shape', { metrics: hasMetric });
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
const metricStyle = {
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
|
||||
return (
|
||||
<g className={className}>
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<rect
|
||||
width={size}
|
||||
height={size}
|
||||
y={-size * 0.5}
|
||||
x={size * 0.5 - height}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
{hasMetric && getClipPathDefinition(clipId, size, height, size * 0.5 - height, -size * 0.5)}
|
||||
{highlighted && <path className="highlighted" {...pathProps(0.7)} />}
|
||||
<path className="border" stroke={color} {...pathProps(0.5)} />
|
||||
<path className="shadow" {...pathProps(0.45)} />
|
||||
<path className="metric-fill" clipPath={`url(#${clipId})`}
|
||||
style={metricStyle} {...pathProps(0.45)} />
|
||||
{highlighted && value !== null ?
|
||||
{hasMetric && <path className="metric-fill" clipPath={`url(#${clipId})`}
|
||||
style={metricStyle} {...pathProps(0.45)} />}
|
||||
{highlighted && hasMetric ?
|
||||
<text style={{fontSize}}>{formattedValue}</text> :
|
||||
<circle className="node" r={Math.max(2, (size * 0.125))} />}
|
||||
</g>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import React from 'react';
|
||||
import d3 from 'd3';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
|
||||
const line = d3.svg.line()
|
||||
.interpolate('cardinal-closed')
|
||||
.tension(0.25);
|
||||
|
||||
|
||||
function getWidth(h) {
|
||||
return (Math.sqrt(3) / 2) * h;
|
||||
}
|
||||
|
||||
|
||||
function getPoints(h) {
|
||||
const w = getWidth(h);
|
||||
const points = [
|
||||
@@ -35,34 +38,23 @@ export default function NodeShapeHex({id, highlighted, size, color, metric}) {
|
||||
|
||||
const shadowSize = 0.45;
|
||||
const upperHexBitHeight = -0.25 * size * shadowSize;
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const metricStyle = {
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
const {height, hasMetric, formattedValue} = getMetricValue(metric, size);
|
||||
const metricStyle = { fill: getMetricColor(metric) };
|
||||
const className = classNames('shape', { metrics: hasMetric });
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
|
||||
return (
|
||||
<g className={className}>
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<rect
|
||||
width={size}
|
||||
height={size}
|
||||
x={size - height + upperHexBitHeight}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
{hasMetric && getClipPathDefinition(clipId, size, height, size - height +
|
||||
upperHexBitHeight, 0)}
|
||||
{highlighted && <path className="highlighted" {...pathProps(0.7)} />}
|
||||
<path className="border" stroke={color} {...pathProps(0.5)} />
|
||||
<path className="shadow" {...pathProps(shadowSize)} />
|
||||
<path className="metric-fill" style={metricStyle}
|
||||
clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />
|
||||
{highlighted && value !== null ?
|
||||
{hasMetric && <path className="metric-fill" style={metricStyle}
|
||||
clipPath={`url(#${clipId})`} {...pathProps(shadowSize)} />}
|
||||
{highlighted && hasMetric ?
|
||||
<text style={{fontSize}}>
|
||||
{formattedValue}
|
||||
</text> :
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import {getMetricValue, getMetricColor} from '../utils/metric-utils.js';
|
||||
import {getMetricValue, getMetricColor, getClipPathDefinition} from '../utils/metric-utils.js';
|
||||
import {CANVAS_METRIC_FONT_SIZE} from '../constants/styles.js';
|
||||
|
||||
|
||||
export default function NodeShapeSquare({
|
||||
id, highlighted, size, color, rx = 0, ry = 0, metric
|
||||
}) {
|
||||
@@ -16,33 +17,20 @@ export default function NodeShapeSquare({
|
||||
});
|
||||
|
||||
const clipId = `mask-${id}`;
|
||||
const {height, value, formattedValue} = getMetricValue(metric, size);
|
||||
const className = classNames('shape', {
|
||||
metrics: value !== null
|
||||
});
|
||||
const {height, hasMetric, formattedValue} = getMetricValue(metric, size);
|
||||
const metricStyle = { fill: getMetricColor(metric) };
|
||||
const className = classNames('shape', { metrics: hasMetric });
|
||||
const fontSize = size * CANVAS_METRIC_FONT_SIZE;
|
||||
const metricStyle = {
|
||||
fill: getMetricColor(metric)
|
||||
};
|
||||
|
||||
return (
|
||||
<g className={className}>
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<rect
|
||||
width={size}
|
||||
height={size}
|
||||
x={-size * 0.5}
|
||||
y={size * 0.5 - height}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
{hasMetric && getClipPathDefinition(clipId, size, height)}
|
||||
{highlighted && <rect className="highlighted" {...rectProps(0.7)} />}
|
||||
<rect className="border" stroke={color} {...rectProps(0.5, 0.5)} />
|
||||
<rect className="shadow" {...rectProps(0.45, 0.39)} />
|
||||
<rect className="metric-fill" style={metricStyle}
|
||||
clipPath={`url(#${clipId})`} {...rectProps(0.45, 0.39)} />
|
||||
{highlighted && value !== null ?
|
||||
{hasMetric && <rect className="metric-fill" style={metricStyle}
|
||||
clipPath={`url(#${clipId})`} {...rectProps(0.45, 0.39)} />}
|
||||
{highlighted && hasMetric ?
|
||||
<text style={{fontSize}}>
|
||||
{formattedValue}
|
||||
</text> :
|
||||
|
||||
@@ -76,7 +76,10 @@ export default class NodesChart extends React.Component {
|
||||
}
|
||||
//
|
||||
// FIXME add PureRenderMixin, Immutables, and move the following functions to render()
|
||||
_.assign(state, this.updateGraphState(nextProps, state));
|
||||
// _.assign(state, this.updateGraphState(nextProps, state));
|
||||
if (nextProps.forceRelayout || nextProps.nodes !== this.props.nodes) {
|
||||
_.assign(state, this.updateGraphState(nextProps, state));
|
||||
}
|
||||
|
||||
if (this.props.selectedNodeId !== nextProps.selectedNodeId) {
|
||||
_.assign(state, this.restoreLayout(state));
|
||||
@@ -131,12 +134,12 @@ export default class NodesChart extends React.Component {
|
||||
return 1;
|
||||
};
|
||||
|
||||
const metric = node => {
|
||||
const met = node.get('metrics') && node.get('metrics')
|
||||
// TODO: think about pulling this up into the store.
|
||||
const metric = node => (
|
||||
node.get('metrics') && node.get('metrics')
|
||||
.filter(m => m.get('id') === this.props.selectedMetric)
|
||||
.first();
|
||||
return met;
|
||||
};
|
||||
.first()
|
||||
);
|
||||
|
||||
return nodes
|
||||
.toIndexedSeq()
|
||||
|
||||
@@ -142,7 +142,7 @@ export default class App extends React.Component {
|
||||
<Status errorUrl={this.state.errorUrl} topology={this.state.currentTopology}
|
||||
topologiesLoaded={this.state.topologiesLoaded}
|
||||
websocketClosed={this.state.websocketClosed} />
|
||||
{this.state.availableCanvasMetrics.length > 0 && <MetricSelector
|
||||
{this.state.availableCanvasMetrics.count() > 0 && <MetricSelector
|
||||
availableCanvasMetrics={this.state.availableCanvasMetrics}
|
||||
pinnedMetric={this.state.pinnedMetric}
|
||||
selectedMetric={this.state.selectedMetric}
|
||||
|
||||
@@ -7,11 +7,14 @@ const log = debug('scope:debug-panel');
|
||||
|
||||
import { receiveNodesDelta } from '../actions/app-actions';
|
||||
import AppStore from '../stores/app-store';
|
||||
import { getNodeColor, getNodeColorDark } from '../utils/color-utils';
|
||||
|
||||
|
||||
const SHAPES = ['square', 'hexagon', 'heptagon', 'circle'];
|
||||
const NODE_COUNTS = [1, 2, 3];
|
||||
const STACK_VARIANTS = [false, true];
|
||||
const METRIC_FILLS = [0, 0.1, 50, 99.9, 100];
|
||||
|
||||
|
||||
const sample = (collection) => _.range(_.random(4)).map(() => _.sample(collection));
|
||||
|
||||
@@ -26,8 +29,11 @@ const shapeTypes = {
|
||||
|
||||
const LABEL_PREFIXES = _.range('A'.charCodeAt(), 'Z'.charCodeAt() + 1)
|
||||
.map(n => String.fromCharCode(n));
|
||||
|
||||
|
||||
const randomLetter = () => _.sample(LABEL_PREFIXES);
|
||||
|
||||
|
||||
const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCount = 1) => ({
|
||||
adjacency,
|
||||
controls: {},
|
||||
@@ -44,6 +50,18 @@ const deltaAdd = (name, adjacency = [], shape = 'circle', stack = false, nodeCou
|
||||
});
|
||||
|
||||
|
||||
function addMetrics(node, v) {
|
||||
const availableMetrics = AppStore.getAvailableCanvasMetrics().toJS();
|
||||
const metrics = availableMetrics.length > 0 ? availableMetrics : [
|
||||
{id: 'host_cpu_usage_percent', label: 'CPU'}
|
||||
];
|
||||
|
||||
return Object.assign({}, node, {
|
||||
metrics: metrics.map(m => Object.assign({}, m, {max: 100, value: v}))
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function label(shape, stacked) {
|
||||
const type = shapeTypes[shape];
|
||||
return stacked ? `Group of ${type[1]}` : type[0];
|
||||
@@ -62,6 +80,17 @@ function addAllVariants() {
|
||||
}
|
||||
|
||||
|
||||
function addAllMetricVariants() {
|
||||
const newNodes = _.flattenDeep(METRIC_FILLS.map((v, i) => (
|
||||
SHAPES.map(s => [addMetrics(deltaAdd(label(s) + i, [], s), v)])
|
||||
)));
|
||||
|
||||
receiveNodesDelta({
|
||||
add: newNodes
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function addNodes(n) {
|
||||
const ns = AppStore.getNodes();
|
||||
const nodeNames = ns.keySeq().toJS();
|
||||
@@ -109,8 +138,10 @@ export class DebugToolbar extends React.Component {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.toggleColors = this.toggleColors.bind(this);
|
||||
this.state = {
|
||||
nodesToAdd: 30
|
||||
nodesToAdd: 30,
|
||||
showColors: false
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,6 +149,12 @@ export class DebugToolbar extends React.Component {
|
||||
this.setState({nodesToAdd: parseInt(ev.target.value, 10)});
|
||||
}
|
||||
|
||||
toggleColors() {
|
||||
this.setState({
|
||||
showColors: !this.state.showColors
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
log('rending debug panel');
|
||||
|
||||
@@ -130,6 +167,7 @@ export class DebugToolbar extends React.Component {
|
||||
<input type="number" onChange={this.onChange} value={this.state.nodesToAdd} />
|
||||
<button onClick={() => addNodes(this.state.nodesToAdd)}>+</button>
|
||||
<button onClick={() => addAllVariants()}>Variants</button>
|
||||
<button onClick={() => addAllMetricVariants()}>Metric Variants</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@@ -139,6 +177,25 @@ export class DebugToolbar extends React.Component {
|
||||
<button onClick={() => enableLog('app-key-press')}>scope:app-key-press</button>
|
||||
<button onClick={() => disableLog()}>Disable log</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Colors</label>
|
||||
<button onClick={this.toggleColors}>toggle</button>
|
||||
</div>
|
||||
|
||||
{this.state.showColors && [getNodeColor, getNodeColorDark].map(fn => (
|
||||
<table>
|
||||
<tbody>
|
||||
{LABEL_PREFIXES.map(r => (
|
||||
<tr key={r}>
|
||||
{LABEL_PREFIXES.map(c => (
|
||||
<td key={c} title={`(${r}, ${c})`} style={{backgroundColor: fn(r, c)}}></td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ export class MetricSelectorItem extends React.Component {
|
||||
}
|
||||
|
||||
onMouseOver() {
|
||||
const k = this.props.metric.id;
|
||||
const k = this.props.metric.get('id');
|
||||
selectMetric(k);
|
||||
}
|
||||
|
||||
onMouseClick() {
|
||||
const k = this.props.metric.id;
|
||||
const k = this.props.metric.get('id');
|
||||
const pinnedMetric = this.props.pinnedMetric;
|
||||
|
||||
if (k === pinnedMetric) {
|
||||
@@ -30,7 +30,7 @@ export class MetricSelectorItem extends React.Component {
|
||||
|
||||
render() {
|
||||
const {metric, selectedMetric, pinnedMetric} = this.props;
|
||||
const id = metric.id;
|
||||
const id = metric.get('id');
|
||||
const isPinned = (id === pinnedMetric);
|
||||
const isSelected = (id === selectedMetric);
|
||||
const className = classNames('metric-selector-action', {
|
||||
@@ -43,7 +43,7 @@ export class MetricSelectorItem extends React.Component {
|
||||
className={className}
|
||||
onMouseOver={this.onMouseOver}
|
||||
onClick={this.onMouseClick}>
|
||||
{metric.label}
|
||||
{metric.get('label')}
|
||||
{isPinned && <span className="fa fa-thumb-tack"></span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -2,11 +2,6 @@ import React from 'react';
|
||||
import { selectMetric } from '../actions/app-actions';
|
||||
import { MetricSelectorItem } from './metric-selector-item';
|
||||
|
||||
// const CROSS = '\u274C';
|
||||
// const MINUS = '\u2212';
|
||||
// const DOT = '\u2022';
|
||||
//
|
||||
|
||||
|
||||
export default class MetricSelector extends React.Component {
|
||||
|
||||
@@ -23,7 +18,7 @@ export default class MetricSelector extends React.Component {
|
||||
const {availableCanvasMetrics} = this.props;
|
||||
|
||||
const items = availableCanvasMetrics.map(metric => (
|
||||
<MetricSelectorItem key={metric.id} metric={metric} {...this.props} />
|
||||
<MetricSelectorItem key={metric.get('id')} metric={metric} {...this.props} />
|
||||
));
|
||||
|
||||
return (
|
||||
|
||||
@@ -61,8 +61,10 @@ let websocketClosed = true;
|
||||
|
||||
let selectedMetric = null;
|
||||
let pinnedMetric = selectedMetric;
|
||||
// class of metric, e.g. 'cpu', rather than 'host_cpu' or 'process_cpu'.
|
||||
// allows us to keep the same metric "type" selected when the topology changes.
|
||||
let pinnedMetricType = null;
|
||||
let availableCanvasMetrics = [];
|
||||
let availableCanvasMetrics = makeList();
|
||||
|
||||
|
||||
const topologySorter = topology => topology.get('rank');
|
||||
@@ -184,7 +186,7 @@ export class AppStore extends Store {
|
||||
}
|
||||
|
||||
getAvailableCanvasMetricsTypes() {
|
||||
return _.fromPairs(this.getAvailableCanvasMetrics().map(m => [m.id, m.label]));
|
||||
return makeMap(this.getAvailableCanvasMetrics().map(m => [m.get('id'), m.get('label')]));
|
||||
}
|
||||
|
||||
getControlStatus() {
|
||||
@@ -404,7 +406,7 @@ export class AppStore extends Store {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
availableCanvasMetrics = [];
|
||||
availableCanvasMetrics = makeList();
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
@@ -415,7 +417,7 @@ export class AppStore extends Store {
|
||||
setTopology(payload.topologyId);
|
||||
nodes = nodes.clear();
|
||||
}
|
||||
availableCanvasMetrics = [];
|
||||
availableCanvasMetrics = makeList();
|
||||
this.__emitChange();
|
||||
break;
|
||||
}
|
||||
@@ -433,7 +435,7 @@ export class AppStore extends Store {
|
||||
}
|
||||
case ActionTypes.PIN_METRIC: {
|
||||
pinnedMetric = payload.metricId;
|
||||
pinnedMetricType = payload.metricType;
|
||||
pinnedMetricType = this.getAvailableCanvasMetricsTypes().get(payload.metricId);
|
||||
selectedMetric = payload.metricId;
|
||||
this.__emitChange();
|
||||
break;
|
||||
@@ -614,13 +616,14 @@ export class AppStore extends Store {
|
||||
makeMap({id: m.get('id'), label: m.get('label')})
|
||||
)))
|
||||
.toSet()
|
||||
.sortBy(m => m.get('label'))
|
||||
.toJS();
|
||||
.toList()
|
||||
.sortBy(m => m.get('label'));
|
||||
|
||||
const similarTypeMetric = availableCanvasMetrics.find(m => m.label === pinnedMetricType);
|
||||
pinnedMetric = similarTypeMetric && similarTypeMetric.id;
|
||||
const similarTypeMetric = availableCanvasMetrics
|
||||
.find(m => m.get('label') === pinnedMetricType);
|
||||
pinnedMetric = similarTypeMetric && similarTypeMetric.get('id');
|
||||
// if something in the current topo is not already selected, select it.
|
||||
if (availableCanvasMetrics.map(m => m.id).indexOf(selectedMetric) === -1) {
|
||||
if (!availableCanvasMetrics.map(m => m.get('id')).toSet().has(selectedMetric)) {
|
||||
selectedMetric = pinnedMetric;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,35 @@
|
||||
import _ from 'lodash';
|
||||
import d3 from 'd3';
|
||||
import { formatMetric } from './string-utils';
|
||||
import { colors } from './color-utils';
|
||||
import { formatMetricSvg } from './string-utils';
|
||||
import { getNodeColorDark as colors } from './color-utils';
|
||||
import React from 'react';
|
||||
|
||||
|
||||
export function getClipPathDefinition(clipId, size, height,
|
||||
x = -size * 0.5, y = size * 0.5 - height) {
|
||||
return (
|
||||
<defs>
|
||||
<clipPath id={clipId}>
|
||||
<rect
|
||||
width={size}
|
||||
height={size}
|
||||
x={x}
|
||||
y={y}
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Open files, 100k should be enought for anyone?
|
||||
const openFilesScale = d3.scale.log().domain([1, 100000]).range([0, 1]);
|
||||
//
|
||||
// loadScale(1) == 0.5; E.g. a nicely balanced system :).
|
||||
const loadScale = d3.scale.log().domain([0.01, 100]).range([0, 1]);
|
||||
|
||||
|
||||
export function getMetricValue(metric, size) {
|
||||
if (!metric) {
|
||||
return {height: 0, value: null, formattedValue: 'n/a'};
|
||||
@@ -17,40 +38,42 @@ export function getMetricValue(metric, size) {
|
||||
const value = m.value;
|
||||
|
||||
let valuePercentage = value === 0 ? 0 : value / m.max;
|
||||
let max = m.max;
|
||||
if (m.id === 'open_files_count') {
|
||||
valuePercentage = openFilesScale(value);
|
||||
max = null;
|
||||
} else if (_.includes(['load1', 'load5', 'load15'], m.id)) {
|
||||
valuePercentage = loadScale(value);
|
||||
max = null;
|
||||
}
|
||||
|
||||
let displayedValue = Number(value).toFixed(1);
|
||||
if (displayedValue > 0) {
|
||||
if (displayedValue > 0 && (!max || displayedValue < max)) {
|
||||
const baseline = 0.1;
|
||||
displayedValue = valuePercentage * (1 - baseline) + baseline;
|
||||
displayedValue = valuePercentage * (1 - baseline * 2) + baseline;
|
||||
} else if (displayedValue >= m.max && displayedValue > 0) {
|
||||
displayedValue = 1;
|
||||
}
|
||||
const height = size * displayedValue;
|
||||
|
||||
return {
|
||||
height,
|
||||
value,
|
||||
formattedValue: formatMetric(value, m, true)
|
||||
hasMetric: value !== null,
|
||||
formattedValue: formatMetricSvg(value, m)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function getMetricColor(metric) {
|
||||
const selectedMetric = metric && metric.get('id');
|
||||
// bluey
|
||||
if (/memory/.test(selectedMetric)) {
|
||||
return '#1f77b4';
|
||||
if (/mem/.test(selectedMetric)) {
|
||||
return colors('p', 'a');
|
||||
} else if (/cpu/.test(selectedMetric)) {
|
||||
return colors('cpu');
|
||||
return colors('z', 'a');
|
||||
} else if (/files/.test(selectedMetric)) {
|
||||
// return colors('files');
|
||||
// purple
|
||||
return '#9467bd';
|
||||
return colors('t', 'a');
|
||||
} else if (/load/.test(selectedMetric)) {
|
||||
return colors('load');
|
||||
return colors('a', 'a');
|
||||
}
|
||||
return 'steelBlue';
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ import React from 'react';
|
||||
import filesize from 'filesize';
|
||||
import d3 from 'd3';
|
||||
|
||||
|
||||
const formatLargeValue = d3.format('s');
|
||||
|
||||
|
||||
function renderHtml(text, unit) {
|
||||
return (
|
||||
<span className="metric-formatted">
|
||||
@@ -14,6 +16,11 @@ function renderHtml(text, unit) {
|
||||
}
|
||||
|
||||
|
||||
function renderSvg(text, unit) {
|
||||
return `${text}${unit}`;
|
||||
}
|
||||
|
||||
|
||||
function makeFormatters(renderFn) {
|
||||
const formatters = {
|
||||
filesize(value) {
|
||||
@@ -45,13 +52,15 @@ function makeFormatters(renderFn) {
|
||||
}
|
||||
|
||||
|
||||
const formatters = makeFormatters(renderHtml);
|
||||
const svgFormatters = makeFormatters((text, unit) => `${text}${unit}`);
|
||||
|
||||
export function formatMetric(value, opts, svg) {
|
||||
const formatterBase = svg ? svgFormatters : formatters;
|
||||
const formatter = opts && formatterBase[opts.format] ? opts.format : 'number';
|
||||
return formatterBase[formatter](value);
|
||||
function makeFormatMetric(renderFn) {
|
||||
const formatters = makeFormatters(renderFn);
|
||||
return (value, opts) => {
|
||||
const formatter = opts && formatters[opts.format] ? opts.format : 'number';
|
||||
return formatters[formatter](value);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export const formatMetric = makeFormatMetric(renderHtml);
|
||||
export const formatMetricSvg = makeFormatMetric(renderSvg);
|
||||
export const formatDate = d3.time.format.iso;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
@base-font: "Roboto", sans-serif;
|
||||
@mono-font: "Menlo", "DejaVu Sans Mono", "Liberation Mono", monospace;
|
||||
|
||||
@base-ease: ease-in-out;
|
||||
|
||||
@primary-color: @weave-charcoal-blue;
|
||||
@background-color: lighten(@primary-color, 66%);
|
||||
@background-lighter-color: lighten(@background-color, 8%);
|
||||
@@ -65,15 +67,15 @@
|
||||
}
|
||||
|
||||
.colorable {
|
||||
transition: background-color .3s ease-in-out;
|
||||
transition: background-color .3s @base-ease;
|
||||
}
|
||||
|
||||
.palable {
|
||||
transition: all .2s ease-in-out;
|
||||
transition: all .2s @base-ease;
|
||||
}
|
||||
|
||||
.hideable {
|
||||
transition: opacity .5s ease-in-out;
|
||||
transition: opacity .5s @base-ease;
|
||||
}
|
||||
|
||||
.hang-around {
|
||||
@@ -221,7 +223,7 @@ h2 {
|
||||
|
||||
&-active {
|
||||
border: 1px solid @text-tertiary-color;
|
||||
animation: blinking 1.5s infinite ease-in-out;
|
||||
animation: blinking 1.5s infinite @base-ease;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +335,7 @@ h2 {
|
||||
|
||||
.nodes > .node {
|
||||
cursor: pointer;
|
||||
transition: opacity .5s ease-in-out;
|
||||
transition: opacity .5s @base-ease;
|
||||
|
||||
&.pseudo {
|
||||
cursor: default;
|
||||
@@ -362,7 +364,7 @@ h2 {
|
||||
}
|
||||
|
||||
.edge {
|
||||
transition: opacity .5s ease-in-out;
|
||||
transition: opacity .5s @base-ease;
|
||||
|
||||
&.blurred {
|
||||
opacity: @edge-opacity-blurred;
|
||||
@@ -402,16 +404,12 @@ h2 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stack .onlyMetrics .shape .metric-fill {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.shape {
|
||||
/* cloud paths have stroke-width set dynamically */
|
||||
&:not(.shape-cloud) .border {
|
||||
stroke-width: @node-border-stroke-width;
|
||||
fill: @background-color;
|
||||
transition: stroke-opacity 0.5s cubic-bezier(0,0,0.21,1), fill 0.5s cubic-bezier(0,0,0.21,1);
|
||||
transition: stroke-opacity 0.333s @base-ease, fill 0.333s @base-ease;
|
||||
stroke-opacity: 1;
|
||||
}
|
||||
|
||||
@@ -423,7 +421,7 @@ h2 {
|
||||
.metric-fill {
|
||||
stroke: none;
|
||||
fill: #A0BE7E;
|
||||
fill-opacity: 0.7;
|
||||
fill-opacity: 0.5;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
@@ -608,7 +606,7 @@ h2 {
|
||||
|
||||
&-icon {
|
||||
margin-right: 0.5em;
|
||||
animation: blinking 2.0s infinite ease-in-out;
|
||||
animation: blinking 2.0s infinite @base-ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -996,7 +994,7 @@ h2 {
|
||||
}
|
||||
|
||||
&.status-loading {
|
||||
animation: blinking 2.0s infinite ease-in-out;
|
||||
animation: blinking 2.0s infinite @base-ease;
|
||||
text-transform: none;
|
||||
color: @text-color;
|
||||
}
|
||||
@@ -1086,4 +1084,15 @@ h2 {
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
table {
|
||||
display: inline-block;
|
||||
border-collapse: collapse;
|
||||
margin: 4px 2px;
|
||||
|
||||
td {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +69,8 @@
|
||||
"coveralls": "cat coverage/lcov.info | coveralls",
|
||||
"lint": "eslint app",
|
||||
"clean": "rm build/app.js",
|
||||
"noprobe": "../scope stop && ../scope launch --no-probe --app.window 24h",
|
||||
"loadreport": "npm run noprobe && sleep 1 && curl -X POST -H \"Content-Type: application/json\" http://$BACKEND_HOST/api/report",
|
||||
"loadreportjson": "npm run loadreport -- -d @../k8s_report.json"
|
||||
"noprobe": "../scope stop && ../scope launch --no-probe --app.window 8760h",
|
||||
"loadreport": "npm run noprobe && sleep 1 && curl -X POST -H \"Content-Type: application/json\" http://$BACKEND_HOST/api/report -d"
|
||||
},
|
||||
"jest": {
|
||||
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
|
||||
|
||||
Reference in New Issue
Block a user