mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-02 17:50:39 +00:00
Merge pull request #2896 from weaveworks/2895-update-eslint-deps
Update eslint dependencies
This commit is contained in:
@@ -36,5 +36,8 @@
|
||||
"react/prefer-stateless-function": 0,
|
||||
"react/sort-comp": 0,
|
||||
"react/prop-types": 0,
|
||||
|
||||
"jsx-a11y/click-events-have-key-events": 0,
|
||||
"jsx-a11y/mouse-events-have-key-events": 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,6 +778,7 @@ export function resetLocalViewState() {
|
||||
return (dispatch) => {
|
||||
dispatch({type: ActionTypes.RESET_LOCAL_VIEW_STATE});
|
||||
storageSet('scopeViewState', '');
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
window.location.href = window.location.href.split('#')[0];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -134,13 +134,17 @@ describe('NodesLayout', () => {
|
||||
},
|
||||
layoutProps: {
|
||||
nodes: fromJS({
|
||||
n1: {id: 'n1', label: 'lold', labelMinor: 'lmold', rank: 'rold'},
|
||||
n1: {
|
||||
id: 'n1', label: 'lold', labelMinor: 'lmold', rank: 'rold'
|
||||
},
|
||||
}),
|
||||
edges: fromJS({})
|
||||
},
|
||||
layoutProps2: {
|
||||
nodes: fromJS({
|
||||
n1: {id: 'n1', label: 'lnew', labelMinor: 'lmnew', rank: 'rnew', x: 111, y: 109},
|
||||
n1: {
|
||||
id: 'n1', label: 'lnew', labelMinor: 'lmnew', rank: 'rnew', x: 111, y: 109
|
||||
},
|
||||
}),
|
||||
edges: fromJS({})
|
||||
}
|
||||
@@ -184,7 +188,8 @@ describe('NodesLayout', () => {
|
||||
it('lays out initial nodeset in a rectangle', () => {
|
||||
const result = NodesLayout.doLayout(
|
||||
nodeSets.initial4.nodes,
|
||||
nodeSets.initial4.edges);
|
||||
nodeSets.initial4.edges
|
||||
);
|
||||
// console.log('initial', result.get('nodes'));
|
||||
nodes = result.nodes.toJS();
|
||||
|
||||
@@ -199,7 +204,8 @@ describe('NodesLayout', () => {
|
||||
it('keeps nodes in rectangle after removing one edge', () => {
|
||||
let result = NodesLayout.doLayout(
|
||||
nodeSets.initial4.nodes,
|
||||
nodeSets.initial4.edges);
|
||||
nodeSets.initial4.edges
|
||||
);
|
||||
|
||||
options.cachedLayout = result;
|
||||
options.nodeCache = options.nodeCache.merge(result.nodes);
|
||||
@@ -221,7 +227,8 @@ describe('NodesLayout', () => {
|
||||
it('keeps nodes in rectangle after removed edge reappears', () => {
|
||||
let result = NodesLayout.doLayout(
|
||||
nodeSets.initial4.nodes,
|
||||
nodeSets.initial4.edges);
|
||||
nodeSets.initial4.edges
|
||||
);
|
||||
|
||||
coords = getNodeCoordinates(result.nodes);
|
||||
options.cachedLayout = result;
|
||||
@@ -252,7 +259,8 @@ describe('NodesLayout', () => {
|
||||
it('keeps nodes in rectangle after node disappears', () => {
|
||||
let result = NodesLayout.doLayout(
|
||||
nodeSets.initial4.nodes,
|
||||
nodeSets.initial4.edges);
|
||||
nodeSets.initial4.edges
|
||||
);
|
||||
|
||||
options.cachedLayout = result;
|
||||
options.nodeCache = options.nodeCache.merge(result.nodes);
|
||||
@@ -273,7 +281,8 @@ describe('NodesLayout', () => {
|
||||
it('keeps nodes in rectangle after removed node reappears', () => {
|
||||
let result = NodesLayout.doLayout(
|
||||
nodeSets.initial4.nodes,
|
||||
nodeSets.initial4.edges);
|
||||
nodeSets.initial4.edges
|
||||
);
|
||||
|
||||
nodes = result.nodes.toJS();
|
||||
|
||||
@@ -313,7 +322,8 @@ describe('NodesLayout', () => {
|
||||
it('renders single nodes in a square', () => {
|
||||
const result = NodesLayout.doLayout(
|
||||
nodeSets.single3.nodes,
|
||||
nodeSets.single3.edges);
|
||||
nodeSets.single3.edges
|
||||
);
|
||||
|
||||
nodes = result.nodes.toJS();
|
||||
|
||||
@@ -404,7 +414,8 @@ describe('NodesLayout', () => {
|
||||
expect(NodesLayout.hasNewNodesOfExistingRank(
|
||||
nodeSets.rank6.nodes,
|
||||
nodeSets.rank6.edges,
|
||||
result.nodes)).toBeTruthy();
|
||||
result.nodes
|
||||
)).toBeTruthy();
|
||||
|
||||
result = NodesLayout.doLayout(
|
||||
nodeSets.rank6.nodes,
|
||||
|
||||
@@ -70,7 +70,9 @@ export default class EdgeContainer extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isAnimated, waypoints, scale, ...forwardedProps } = this.props;
|
||||
const {
|
||||
isAnimated, waypoints, scale, ...forwardedProps
|
||||
} = this.props;
|
||||
const { thickness, waypointsMap } = this.state;
|
||||
|
||||
if (!isAnimated) {
|
||||
@@ -81,9 +83,14 @@ export default class EdgeContainer extends React.PureComponent {
|
||||
// For the Motion interpolation to work, the waypoints need to be in a map format like
|
||||
// { x0: 11, y0: 22, x1: 33, y1: 44 } that we convert to the array format when rendering.
|
||||
<Motion style={{ interpolatedThickness: weakSpring(thickness), ...waypointsMap.toJS() }}>
|
||||
{({ interpolatedThickness, ...interpolatedWaypoints}) => transformedEdge(
|
||||
forwardedProps, waypointsMapToArray(fromJS(interpolatedWaypoints)), interpolatedThickness
|
||||
)}
|
||||
{
|
||||
({ interpolatedThickness, ...interpolatedWaypoints}) =>
|
||||
transformedEdge(
|
||||
forwardedProps,
|
||||
waypointsMapToArray(fromJS(interpolatedWaypoints)),
|
||||
interpolatedThickness
|
||||
)
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { enterEdge, leaveEdge } from '../actions/app-actions';
|
||||
import { encodeIdAttribute, decodeIdAttribute } from '../utils/dom-utils';
|
||||
|
||||
class Edge extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleMouseEnter = this.handleMouseEnter.bind(this);
|
||||
@@ -14,13 +13,16 @@ class Edge extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, path, highlighted, focused, thickness, source, target } = this.props;
|
||||
const {
|
||||
id, path, highlighted, focused, thickness, source, target
|
||||
} = this.props;
|
||||
const shouldRenderMarker = (focused || highlighted) && (source !== target);
|
||||
const className = classNames('edge', { highlighted });
|
||||
|
||||
return (
|
||||
<g
|
||||
id={encodeIdAttribute(id)} className={className}
|
||||
id={encodeIdAttribute(id)}
|
||||
className={className}
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
>
|
||||
|
||||
@@ -13,7 +13,9 @@ const transformedNode = (otherProps, { x, y, k }) => (
|
||||
|
||||
export default class NodeContainer extends React.PureComponent {
|
||||
render() {
|
||||
const { dx, dy, isAnimated, scale, ...forwardedProps } = this.props;
|
||||
const {
|
||||
dx, dy, isAnimated, scale, ...forwardedProps
|
||||
} = this.props;
|
||||
|
||||
if (!isAnimated) {
|
||||
// Show static node for optimized rendering
|
||||
|
||||
@@ -23,7 +23,9 @@ import {
|
||||
import { encodeIdAttribute } from '../utils/dom-utils';
|
||||
|
||||
|
||||
function NodeShape(shapeType, shapeElement, shapeProps, { id, highlighted, color, metric }) {
|
||||
function NodeShape(shapeType, shapeElement, shapeProps, {
|
||||
id, highlighted, color, metric
|
||||
}) {
|
||||
const { height, hasMetric, formattedValue } = getMetricValue(metric);
|
||||
const className = classNames('shape', `shape-${shapeType}`, { metrics: hasMetric });
|
||||
const metricStyle = { fill: getMetricColor(metric) };
|
||||
|
||||
@@ -103,8 +103,10 @@ class Node extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { focused, highlighted, networks, pseudo, rank, label, transform,
|
||||
exportingGraph, showingNetworks, stack, id, metric } = this.props;
|
||||
const {
|
||||
focused, highlighted, networks, pseudo, rank, label, transform,
|
||||
exportingGraph, showingNetworks, stack, id, metric
|
||||
} = this.props;
|
||||
const { hovered } = this.state;
|
||||
|
||||
const color = getNodeColor(rank, label, pseudo);
|
||||
|
||||
@@ -196,9 +196,14 @@ class NodesChartElements extends React.Component {
|
||||
const scale = (this.props.selectedScale || 1) * 100000;
|
||||
return (
|
||||
<rect
|
||||
className={className} key="nodes-chart-overlay"
|
||||
transform={`scale(${scale})`} fill="#fff"
|
||||
x={-1} y={-1} width={2} height={2}
|
||||
className={className}
|
||||
key="nodes-chart-overlay"
|
||||
transform={`scale(${scale})`}
|
||||
fill="#fff"
|
||||
x={-1}
|
||||
y={-1}
|
||||
width={2}
|
||||
height={2}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -277,6 +282,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(NodesChartElements);
|
||||
export default connect(mapStateToProps)(NodesChartElements);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
export default function NodesError({children, faIconClass, hidden,
|
||||
mainClassName = 'nodes-chart-error'}) {
|
||||
const NodesError = ({
|
||||
children, faIconClass, hidden, mainClassName = 'nodes-chart-error'
|
||||
}) => {
|
||||
const className = classnames(mainClassName, {
|
||||
hide: hidden
|
||||
});
|
||||
@@ -18,4 +19,6 @@ export default function NodesError({children, faIconClass, hidden,
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default NodesError;
|
||||
|
||||
@@ -58,7 +58,9 @@ function getColumns(nodes) {
|
||||
}
|
||||
|
||||
|
||||
function renderIdCell({ rank, label, labelMinor, pseudo }) {
|
||||
function renderIdCell({
|
||||
rank, label, labelMinor, pseudo
|
||||
}) {
|
||||
const showSubLabel = Boolean(pseudo) && labelMinor;
|
||||
const title = showSubLabel ? `${label} (${labelMinor})` : label;
|
||||
const iconStyle = {
|
||||
@@ -79,7 +81,6 @@ function renderIdCell({ rank, label, labelMinor, pseudo }) {
|
||||
|
||||
|
||||
class NodesGrid extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -101,8 +102,10 @@ class NodesGrid extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nodes, height, gridSortedBy, gridSortedDesc, canvasMargins,
|
||||
searchNodeMatches, searchQuery } = this.props;
|
||||
const {
|
||||
nodes, height, gridSortedBy, gridSortedDesc, canvasMargins,
|
||||
searchNodeMatches, searchQuery
|
||||
} = this.props;
|
||||
const cmpStyle = {
|
||||
height,
|
||||
marginTop: canvasMargins.top,
|
||||
|
||||
@@ -88,7 +88,7 @@ function layoutSingleNodes(layout, opts) {
|
||||
const graphWidth = layout.graphWidth || layout.width;
|
||||
const aspectRatio = graphHeight ? graphWidth / graphHeight : 1;
|
||||
|
||||
let nodes = layout.nodes;
|
||||
let { nodes } = layout;
|
||||
|
||||
// 0-degree nodes
|
||||
const singleNodes = nodes.filter(node => node.get('degree') === 0);
|
||||
|
||||
@@ -19,28 +19,31 @@ describe('NodeDetails', () => {
|
||||
});
|
||||
|
||||
it('shows n/a when node was not found', () => {
|
||||
const c = TestUtils.renderIntoDocument(
|
||||
const c = TestUtils.renderIntoDocument((
|
||||
<Provider store={configureStore()}>
|
||||
<NodeDetails notFound />
|
||||
</Provider>
|
||||
));
|
||||
const notFound = TestUtils.findRenderedDOMComponentWithClass(
|
||||
c,
|
||||
'node-details-header-notavailable'
|
||||
);
|
||||
const notFound = TestUtils.findRenderedDOMComponentWithClass(c,
|
||||
'node-details-header-notavailable');
|
||||
expect(notFound).toBeDefined();
|
||||
});
|
||||
|
||||
it('show label of node with title', () => {
|
||||
nodes = nodes.set(nodeId, Immutable.fromJS({id: nodeId}));
|
||||
details = {label: 'Node 1'};
|
||||
const c = TestUtils.renderIntoDocument(
|
||||
const c = TestUtils.renderIntoDocument((
|
||||
<Provider store={configureStore()}>
|
||||
<NodeDetails
|
||||
nodes={nodes}
|
||||
topologyId="containers"
|
||||
nodeId={nodeId} details={details}
|
||||
/>
|
||||
nodeId={nodeId}
|
||||
details={details}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
));
|
||||
|
||||
const title = TestUtils.findRenderedDOMComponentWithClass(c, 'node-details-header-label');
|
||||
expect(title.title).toBe('Node 1');
|
||||
|
||||
@@ -170,9 +170,11 @@ class App extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails,
|
||||
const {
|
||||
isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails,
|
||||
showingHelp, showingNetworkSelector, showingTroubleshootingMenu,
|
||||
timeTravelTransitioning, showingTimeTravel } = this.props;
|
||||
timeTravelTransitioning, showingTimeTravel
|
||||
} = this.props;
|
||||
|
||||
const className = classNames('scope-app', { 'time-travel-open': showingTimeTravel });
|
||||
const isIframe = window !== window.top;
|
||||
@@ -192,9 +194,11 @@ class App extends React.Component {
|
||||
<TimeTravel />
|
||||
<div className="selectors">
|
||||
<div className="logo">
|
||||
{!isIframe && <svg width="100%" height="100%" viewBox="0 0 1089 217">
|
||||
<Logo />
|
||||
</svg>}
|
||||
{!isIframe &&
|
||||
<svg width="100%" height="100%" viewBox="0 0 1089 217">
|
||||
<Logo />
|
||||
</svg>
|
||||
}
|
||||
</div>
|
||||
<Search />
|
||||
<Topologies />
|
||||
@@ -243,6 +247,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(App);
|
||||
export default connect(mapStateToProps)(App);
|
||||
|
||||
@@ -21,7 +21,7 @@ const CloudLink = ({ alwaysShow, ...props }) => (
|
||||
<CloudFeature alwaysShow={alwaysShow}>
|
||||
<LinkWrapper {...props} />
|
||||
</CloudFeature>
|
||||
);
|
||||
);
|
||||
|
||||
class LinkWrapper extends React.Component {
|
||||
constructor(props, context) {
|
||||
@@ -44,7 +44,7 @@ class LinkWrapper extends React.Component {
|
||||
if (router && href[0] === '/') {
|
||||
router.push(href);
|
||||
} else {
|
||||
location.href = href;
|
||||
window.location.href = href;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ function startPerf(delay) {
|
||||
|
||||
export function showingDebugToolbar() {
|
||||
return (('debugToolbar' in localStorage && JSON.parse(localStorage.debugToolbar))
|
||||
|| location.pathname.indexOf('debug') > -1);
|
||||
|| window.location.pathname.indexOf('debug') > -1);
|
||||
}
|
||||
|
||||
|
||||
@@ -151,7 +151,6 @@ function setAppState(fn) {
|
||||
|
||||
|
||||
class DebugToolbar extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
@@ -265,7 +264,9 @@ class DebugToolbar extends React.Component {
|
||||
addInternetNode() {
|
||||
setTimeout(() => {
|
||||
this.asyncDispatch(receiveNodesDelta({
|
||||
add: [{id: INTERNET, label: INTERNET, pseudo: true, labelMinor: 'Outgoing packets', shape: 'cloud'}]
|
||||
add: [{
|
||||
id: INTERNET, label: INTERNET, pseudo: true, labelMinor: 'Outgoing packets', shape: 'cloud'
|
||||
}]
|
||||
}));
|
||||
}, 0);
|
||||
}
|
||||
@@ -385,6 +386,4 @@ function mapStateToProps(state) {
|
||||
}
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(DebugToolbar);
|
||||
export default connect(mapStateToProps)(DebugToolbar);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
} from '../constants/styles';
|
||||
|
||||
class DetailsCard extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
|
||||
@@ -11,8 +11,11 @@ class Details extends React.Component {
|
||||
<div className="details">
|
||||
{details.toIndexedSeq().map((obj, index) => (
|
||||
<DetailsCard
|
||||
key={obj.id} index={index} cardCount={details.size}
|
||||
nodeControlStatus={controlStatus.get(obj.id)} {...obj}
|
||||
key={obj.id}
|
||||
index={index}
|
||||
cardCount={details.size}
|
||||
nodeControlStatus={controlStatus.get(obj.id)}
|
||||
{...obj}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -27,6 +30,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(Details);
|
||||
export default connect(mapStateToProps)(Details);
|
||||
|
||||
@@ -3,11 +3,11 @@ import { createDevTools } from 'redux-devtools';
|
||||
import LogMonitor from 'redux-devtools-log-monitor';
|
||||
import DockMonitor from 'redux-devtools-dock-monitor';
|
||||
|
||||
export default createDevTools(
|
||||
export default createDevTools((
|
||||
<DockMonitor
|
||||
defaultIsVisible={false}
|
||||
toggleVisibilityKey="ctrl-h"
|
||||
changePositionKey="ctrl-w">
|
||||
<LogMonitor />
|
||||
</DockMonitor>
|
||||
);
|
||||
));
|
||||
|
||||
@@ -6,7 +6,6 @@ import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS } from '../constants/styles'
|
||||
import Terminal from './terminal';
|
||||
|
||||
class EmeddedTerminal extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
@@ -70,6 +69,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(EmeddedTerminal);
|
||||
export default connect(mapStateToProps)(EmeddedTerminal);
|
||||
|
||||
@@ -34,7 +34,9 @@ class Footer extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { hostname, version, versionUpdate, contrastMode } = this.props;
|
||||
const {
|
||||
hostname, version, versionUpdate, contrastMode
|
||||
} = this.props;
|
||||
|
||||
const otherContrastModeTitle = contrastMode
|
||||
? 'Switch to normal contrast' : 'Switch to high contrast';
|
||||
@@ -47,13 +49,16 @@ class Footer extends React.Component {
|
||||
return (
|
||||
<div className="footer">
|
||||
<div className="footer-status">
|
||||
{versionUpdate && <a
|
||||
className="footer-versionupdate"
|
||||
title={versionUpdateTitle}
|
||||
href={versionUpdate.get('downloadUrl')}
|
||||
target="_blank" rel="noopener noreferrer">
|
||||
Update available: {versionUpdate.get('version')}
|
||||
</a>}
|
||||
{versionUpdate &&
|
||||
<a
|
||||
className="footer-versionupdate"
|
||||
title={versionUpdateTitle}
|
||||
href={versionUpdate.get('downloadUrl')}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
Update available: {versionUpdate.get('version')}
|
||||
</a>
|
||||
}
|
||||
<span className="footer-label">Version</span>
|
||||
{version}
|
||||
<span className="footer-label">on</span>
|
||||
@@ -65,25 +70,26 @@ class Footer extends React.Component {
|
||||
</div>
|
||||
|
||||
<div className="footer-tools">
|
||||
<a
|
||||
<button
|
||||
className="footer-icon"
|
||||
onClick={this.handleRelayoutClick}
|
||||
title={forceRelayoutTitle}>
|
||||
<span className="fa fa-refresh" />
|
||||
</a>
|
||||
<a onClick={this.handleContrastClick} className="footer-icon" title={otherContrastModeTitle}>
|
||||
</button>
|
||||
<button onClick={this.handleContrastClick} className="footer-icon" title={otherContrastModeTitle}>
|
||||
<span className="fa fa-adjust" />
|
||||
</a>
|
||||
<a
|
||||
</button>
|
||||
<button
|
||||
onClick={this.props.toggleTroubleshootingMenu}
|
||||
className="footer-icon" title="Open troubleshooting menu"
|
||||
className="footer-icon"
|
||||
title="Open troubleshooting menu"
|
||||
href=""
|
||||
>
|
||||
<span className="fa fa-bug" />
|
||||
</a>
|
||||
<a className="footer-icon" onClick={this.props.toggleHelp} title="Show help">
|
||||
</button>
|
||||
<button className="footer-icon" onClick={this.props.toggleHelp} title="Show help">
|
||||
<span className="fa fa-question" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -116,14 +116,21 @@ function renderSearchPanel() {
|
||||
|
||||
|
||||
function renderFieldsPanel(currentTopologyName, searchableFields) {
|
||||
const none = <span style={{fontStyle: 'italic'}}>None</span>;
|
||||
const none = (
|
||||
<span style={{fontStyle: 'italic'}}>None</span>
|
||||
);
|
||||
const currentTopology = (
|
||||
<span className="help-panel-fields-current-topology">
|
||||
{currentTopologyName}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="help-panel-fields">
|
||||
<h2>Fields and Metrics</h2>
|
||||
<p>
|
||||
Searchable fields and metrics in the <br />
|
||||
currently selected <span className="help-panel-fields-current-topology">
|
||||
{currentTopologyName}</span> topology:
|
||||
currently selected {currentTopology} topology:
|
||||
</p>
|
||||
<div className="help-panel-fields-fields">
|
||||
<div className="help-panel-fields-fields-column">
|
||||
@@ -150,7 +157,9 @@ function renderFieldsPanel(currentTopologyName, searchableFields) {
|
||||
}
|
||||
|
||||
|
||||
function HelpPanel({ currentTopologyName, searchableFields, onClickClose, canvasMargins }) {
|
||||
function HelpPanel({
|
||||
currentTopologyName, searchableFields, onClickClose, canvasMargins
|
||||
}) {
|
||||
return (
|
||||
<div className="help-panel-wrapper">
|
||||
<div className="help-panel" style={{marginTop: canvasMargins.top}}>
|
||||
|
||||
@@ -36,7 +36,6 @@ function renderTemplate(nodeType, template) {
|
||||
|
||||
|
||||
export class Loading extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -54,5 +53,4 @@ export class Loading extends React.Component {
|
||||
</NodesError>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,50 +7,63 @@ export default function Logo({ transform = '' }) {
|
||||
<g className="logo" transform={transform}>
|
||||
<path fill="#32324B" d="M114.937,118.165l75.419-67.366c-5.989-4.707-12.71-8.52-19.981-11.211l-55.438,49.52V118.165z" />
|
||||
<path fill="#32324B" d="M93.265,108.465l-20.431,18.25c1.86,7.57,4.88,14.683,8.87,21.135l11.561-10.326V108.465z" />
|
||||
<path fill="#00D2FF" d="M155.276,53.074V35.768C151.815,35.27,148.282,35,144.685,35c-3.766,0-7.465,0.286-11.079,0.828v36.604
|
||||
<path fill="#00D2FF"
|
||||
d="M155.276,53.074V35.768C151.815,35.27,148.282,35,144.685,35c-3.766,0-7.465,0.286-11.079,0.828v36.604
|
||||
L155.276,53.074z" />
|
||||
<path fill="#00D2FF" d="M155.276,154.874V82.133l-21.671,19.357v80.682c3.614,0.543,7.313,0.828,11.079,0.828
|
||||
<path fill="#00D2FF"
|
||||
d="M155.276,154.874V82.133l-21.671,19.357v80.682c3.614,0.543,7.313,0.828,11.079,0.828
|
||||
c4.41,0,8.723-0.407,12.921-1.147l58.033-51.838c1.971-6.664,3.046-13.712,3.046-21.015c0-3.439-0.254-6.817-0.708-10.132
|
||||
L155.276,154.874z" />
|
||||
<path fill="#FF4B19" d="M155.276,133.518l58.14-51.933c-2.77-6.938-6.551-13.358-11.175-19.076l-46.965,41.951V133.518z" />
|
||||
<path fill="#FF4B19" d="M133.605,123.817l-18.668,16.676V41.242c-8.086,3.555-15.409,8.513-21.672,14.567V162.19
|
||||
<path fill="#FF4B19"
|
||||
d="M133.605,123.817l-18.668,16.676V41.242c-8.086,3.555-15.409,8.513-21.672,14.567V162.19
|
||||
c4.885,4.724,10.409,8.787,16.444,12.03l23.896-21.345V123.817z" />
|
||||
<polygon fill="#32324B" points="325.563,124.099 339.389,72.22 357.955,72.22 337.414,144.377 315.556,144.377 303.311,95.79
|
||||
<polygon fill="#32324B"
|
||||
points="325.563,124.099 339.389,72.22 357.955,72.22 337.414,144.377 315.556,144.377 303.311,95.79
|
||||
291.065,144.377 269.207,144.377 248.666,72.22 267.232,72.22 281.058,124.099 294.752,72.22 311.869,72.22 " />
|
||||
<path fill="#32324B" d="M426.429,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
<path fill="#32324B"
|
||||
d="M426.429,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
|
||||
c7.242,0,12.904-3.555,14.353-10.27L426.429,120.676z M408.654,99.608c-0.659-10.008-7.11-13.694-14.484-13.694
|
||||
c-8.427,0-14.879,5.135-15.801,13.694H408.654z" />
|
||||
<path fill="#32324B" d="M480.628,97.634v-2.502c0-5.662-2.37-9.351-13.036-9.351c-13.298,0-13.694,7.375-13.694,9.877h-17.117
|
||||
<path fill="#32324B"
|
||||
d="M480.628,97.634v-2.502c0-5.662-2.37-9.351-13.036-9.351c-13.298,0-13.694,7.375-13.694,9.877h-17.117
|
||||
c0-10.666,4.477-24.359,31.338-24.359c25.676,0,30.285,12.771,30.285,23.174v39.766c0,2.897,0.131,5.267,0.395,7.11l0.527,3.028
|
||||
h-18.172v-7.241c-5.134,5.134-12.245,8.163-22.384,8.163c-14.221,0-25.018-8.296-25.018-22.648c0-16.59,15.67-20.146,21.99-21.199
|
||||
L480.628,97.634z M480.628,111.195l-6.979,1.054c-3.819,0.658-8.427,1.315-11.192,1.843c-3.029,0.527-5.662,1.186-7.637,2.765
|
||||
c-1.844,1.449-2.765,3.425-2.765,5.926c0,2.107,0.79,8.69,10.666,8.69c5.793,0,10.928-2.105,13.693-4.872
|
||||
c3.556-3.555,4.214-8.032,4.214-11.587V111.195z" />
|
||||
<polygon fill="#32324B" points="549.495,144.377 525.399,144.377 501.698,72.221 521.186,72.221 537.775,127.392 554.499,72.221
|
||||
<polygon fill="#32324B"
|
||||
points="549.495,144.377 525.399,144.377 501.698,72.221 521.186,72.221 537.775,127.392 554.499,72.221
|
||||
573.459,72.221 " />
|
||||
<path fill="#32324B" d="M641.273,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
<path fill="#32324B"
|
||||
d="M641.273,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
|
||||
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
|
||||
c7.242,0,12.904-3.555,14.354-10.27L641.273,120.676z M623.498,99.608c-0.659-10.008-7.109-13.694-14.483-13.694
|
||||
c-8.428,0-14.88,5.135-15.802,13.694H623.498z" />
|
||||
<path fill="#32324B" d="M682.976,80.873c-7.524,0-16.896,2.376-16.896,10.692c0,17.952,46.201,1.452,46.201,30.229
|
||||
<path fill="#32324B"
|
||||
d="M682.976,80.873c-7.524,0-16.896,2.376-16.896,10.692c0,17.952,46.201,1.452,46.201,30.229
|
||||
c0,9.637-5.676,22.309-30.229,22.309c-19.009,0-27.721-9.636-28.249-22.44h11.881c0.264,7.788,5.147,13.332,17.688,13.332
|
||||
c14.52,0,17.952-6.204,17.952-12.54c0-13.332-24.421-7.788-37.753-15.181c-4.885-2.771-8.316-7.128-8.316-15.048
|
||||
c0-11.616,10.824-20.461,27.853-20.461c20.989,0,27.193,12.145,27.589,20.196h-11.484
|
||||
C698.685,83.381,691.556,80.873,682.976,80.873z" />
|
||||
<path fill="#32324B" d="M756.233,134.994c10.429,0,17.953-5.939,19.009-16.632h10.957c-1.98,17.028-13.597,25.74-29.966,25.74
|
||||
<path fill="#32324B"
|
||||
d="M756.233,134.994c10.429,0,17.953-5.939,19.009-16.632h10.957c-1.98,17.028-13.597,25.74-29.966,25.74
|
||||
c-18.744,0-32.076-12.012-32.076-35.905c0-23.76,13.464-36.433,32.209-36.433c16.104,0,27.721,8.712,29.568,25.213h-10.956
|
||||
c-1.452-11.353-9.24-16.104-18.877-16.104c-12.012,0-20.856,8.448-20.856,27.324C735.245,127.471,744.485,134.994,756.233,134.994z
|
||||
" />
|
||||
<path fill="#32324B" d="M830.418,144.103c-19.141,0-32.341-12.145-32.341-36.169c0-23.893,13.2-36.169,32.341-36.169
|
||||
<path fill="#32324B"
|
||||
d="M830.418,144.103c-19.141,0-32.341-12.145-32.341-36.169c0-23.893,13.2-36.169,32.341-36.169
|
||||
c19.009,0,32.209,12.145,32.209,36.169C862.627,132.091,849.427,144.103,830.418,144.103z M830.418,134.994
|
||||
c12.145,0,21.12-7.392,21.12-27.061c0-19.536-8.976-27.061-21.12-27.061c-12.276,0-21.253,7.393-21.253,27.061
|
||||
C809.165,127.603,818.142,134.994,830.418,134.994z" />
|
||||
<path fill="#32324B" d="M888.629,72.688v10.692c3.96-6.732,12.54-11.616,22.969-11.616c19.009,0,30.757,12.673,30.757,36.169
|
||||
<path fill="#32324B"
|
||||
d="M888.629,72.688v10.692c3.96-6.732,12.54-11.616,22.969-11.616c19.009,0,30.757,12.673,30.757,36.169
|
||||
c0,23.629-12.145,36.169-31.152,36.169c-10.429,0-18.745-4.224-22.573-11.22v35.641h-10.824V72.688H888.629z M910.409,134.994
|
||||
c12.145,0,20.857-7.392,20.857-27.061c0-19.536-8.713-27.061-20.857-27.061c-12.275,0-21.912,7.393-21.912,27.061
|
||||
C888.497,127.603,898.134,134.994,910.409,134.994z" />
|
||||
<path fill="#32324B" d="M1016.801,119.022c-1.452,12.408-10.032,25.08-30.229,25.08c-18.745,0-32.341-12.804-32.341-36.037
|
||||
<path fill="#32324B"
|
||||
d="M1016.801,119.022c-1.452,12.408-10.032,25.08-30.229,25.08c-18.745,0-32.341-12.804-32.341-36.037
|
||||
c0-21.912,13.464-36.301,32.209-36.301c19.8,0,30.757,14.784,30.757,38.018h-51.878c0.265,13.332,5.809,25.212,21.385,25.212
|
||||
c11.484,0,18.217-7.128,19.141-16.104L1016.801,119.022z M1005.448,101.201c-1.056-14.916-9.636-20.328-19.272-20.328
|
||||
c-10.824,0-19.141,7.26-20.46,20.328H1005.448z" />
|
||||
|
||||
@@ -13,7 +13,8 @@ const Match = match => (
|
||||
{match.label}:
|
||||
</span>
|
||||
<MatchedText
|
||||
text={match.text} match={match}
|
||||
text={match.text}
|
||||
match={match}
|
||||
maxLength={MAX_MATCH_LENGTH}
|
||||
truncate={match.truncate} />
|
||||
</div>
|
||||
@@ -41,9 +42,11 @@ export default class MatchedResults extends React.PureComponent {
|
||||
return (
|
||||
<div className="matched-results" style={style}>
|
||||
{matches.keySeq().take(SHOW_ROW_COUNT).map(fieldId => Match(matches.get(fieldId)))}
|
||||
{moreFieldMatches && <div className="matched-results-more" title={moreFieldMatchesTitle}>
|
||||
{`${moreFieldMatches.size} more matches`}
|
||||
</div>}
|
||||
{moreFieldMatches &&
|
||||
<div className="matched-results-more" title={moreFieldMatchesTitle}>
|
||||
{`${moreFieldMatches.size} more matches`}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const TRUNCATE_ELLIPSIS = '…';
|
||||
* `('text', {start: 2, length: 1}) => [{text: 'te'}, {text: 'x', match: true}, {text: 't'}]`
|
||||
*/
|
||||
function chunkText(text, { start, length }) {
|
||||
if (text && !isNaN(start) && !isNaN(length)) {
|
||||
if (text && !window.isNaN(start) && !window.isNaN(length)) {
|
||||
const chunks = [];
|
||||
// text chunk before match
|
||||
if (start > 0) {
|
||||
@@ -80,7 +80,9 @@ function truncateChunks(chunks, text, maxLength) {
|
||||
*/
|
||||
export default class MatchedText extends React.PureComponent {
|
||||
render() {
|
||||
const { match, text, truncate, maxLength } = this.props;
|
||||
const {
|
||||
match, text, truncate, maxLength
|
||||
} = this.props;
|
||||
|
||||
const showFullValue = !truncate || (match && (match.start + match.length) > truncate);
|
||||
const displayText = showFullValue ? text : text.slice(0, truncate);
|
||||
|
||||
@@ -31,7 +31,7 @@ class MetricSelectorItem extends React.Component {
|
||||
|
||||
onMouseClick() {
|
||||
const metricType = this.props.metric.get('label');
|
||||
const pinnedMetricType = this.props.pinnedMetricType;
|
||||
const { pinnedMetricType } = this.props;
|
||||
|
||||
if (metricType !== pinnedMetricType) {
|
||||
this.trackEvent('scope.metric.selector.pin.click');
|
||||
|
||||
@@ -22,14 +22,16 @@ class MetricSelector extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="metric-selector">
|
||||
{hasMetrics && <div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
|
||||
{availableMetrics.map(metric => (
|
||||
<MetricSelectorItem
|
||||
key={metric.get('id')}
|
||||
metric={metric}
|
||||
/>
|
||||
))}
|
||||
</div>}
|
||||
{hasMetrics &&
|
||||
<div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
|
||||
{availableMetrics.map(metric => (
|
||||
<MetricSelectorItem
|
||||
key={metric.get('id')}
|
||||
metric={metric}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { selectNetwork, pinNetwork, unpinNetwork } from '../actions/app-actions'
|
||||
import { getNetworkColor } from '../utils/color-utils';
|
||||
|
||||
class NetworkSelectorItem extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -21,7 +20,7 @@ class NetworkSelectorItem extends React.Component {
|
||||
|
||||
onMouseClick() {
|
||||
const k = this.props.network.get('id');
|
||||
const pinnedNetwork = this.props.pinnedNetwork;
|
||||
const { pinnedNetwork } = this.props;
|
||||
|
||||
if (k === pinnedNetwork) {
|
||||
this.props.unpinNetwork(k);
|
||||
|
||||
@@ -7,7 +7,6 @@ import { availableNetworksSelector } from '../selectors/node-networks';
|
||||
import NetworkSelectorItem from './network-selector-item';
|
||||
|
||||
class NetworkSelector extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
|
||||
@@ -63,12 +63,14 @@ class NodeDetails extends React.Component {
|
||||
return (
|
||||
<div className="node-details-tools-wrapper">
|
||||
<div className="node-details-tools">
|
||||
{showSwitchTopology && <span
|
||||
title={topologyTitle}
|
||||
className="fa fa-long-arrow-left"
|
||||
onClick={this.handleShowTopologyForNode}>
|
||||
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
|
||||
</span>}
|
||||
{showSwitchTopology &&
|
||||
<span
|
||||
title={topologyTitle}
|
||||
className="fa fa-long-arrow-left"
|
||||
onClick={this.handleShowTopologyForNode}>
|
||||
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
|
||||
</span>
|
||||
}
|
||||
<span
|
||||
title="Close details"
|
||||
className="fa fa-close close-details"
|
||||
@@ -87,8 +89,8 @@ class NodeDetails extends React.Component {
|
||||
// caused by a bug having to do with animating the details panel).
|
||||
const spinnerClassName = classNames('fa fa-circle-o-notch', { 'fa-spin': this.props.mounted });
|
||||
const nodeColor = (node ?
|
||||
getNodeColorDark(node.get('rank'), label, node.get('pseudo')) :
|
||||
getNeutralColor());
|
||||
getNodeColorDark(node.get('rank'), label, node.get('pseudo')) :
|
||||
getNeutralColor());
|
||||
const tools = this.renderTools();
|
||||
const styles = {
|
||||
header: {
|
||||
@@ -158,7 +160,9 @@ class NodeDetails extends React.Component {
|
||||
}
|
||||
|
||||
renderDetails() {
|
||||
const { details, nodeControlStatus, nodeMatches = makeMap(), topologyId } = this.props;
|
||||
const {
|
||||
details, nodeControlStatus, nodeMatches = makeMap(), topologyId
|
||||
} = this.props;
|
||||
const showControls = details.controls && details.controls.length > 0;
|
||||
const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
|
||||
const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
|
||||
@@ -188,35 +192,42 @@ class NodeDetails extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showControls && <div className="node-details-controls-wrapper" style={styles.controls}>
|
||||
<NodeDetailsControls
|
||||
nodeId={this.props.nodeId}
|
||||
controls={details.controls}
|
||||
pending={pending}
|
||||
error={error} />
|
||||
</div>}
|
||||
{showControls &&
|
||||
<div className="node-details-controls-wrapper" style={styles.controls}>
|
||||
<NodeDetailsControls
|
||||
nodeId={this.props.nodeId}
|
||||
controls={details.controls}
|
||||
pending={pending}
|
||||
error={error} />
|
||||
</div>
|
||||
}
|
||||
|
||||
<div className="node-details-content">
|
||||
{details.metrics && <div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Status</div>
|
||||
<NodeDetailsHealth
|
||||
metrics={details.metrics}
|
||||
topologyId={topologyId}
|
||||
/>
|
||||
</div>}
|
||||
{details.metadata && <div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Info</div>
|
||||
<NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
|
||||
</div>}
|
||||
{details.metrics &&
|
||||
<div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Status</div>
|
||||
<NodeDetailsHealth
|
||||
metrics={details.metrics}
|
||||
topologyId={topologyId}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
{details.metadata &&
|
||||
<div className="node-details-content-section">
|
||||
<div className="node-details-content-section-header">Info</div>
|
||||
<NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
|
||||
</div>
|
||||
}
|
||||
|
||||
{details.connections && details.connections.filter(cs => cs.connections.length > 0)
|
||||
.map(connections => (<div className="node-details-content-section" key={connections.id}>
|
||||
<NodeDetailsTable
|
||||
{...connections}
|
||||
nodes={connections.connections}
|
||||
nodeIdKey="nodeId"
|
||||
/>
|
||||
</div>
|
||||
.map(connections => (
|
||||
<div className="node-details-content-section" key={connections.id}>
|
||||
<NodeDetailsTable
|
||||
{...connections}
|
||||
nodes={connections.connections}
|
||||
nodeIdKey="nodeId"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{details.children && details.children.map(children => (
|
||||
@@ -231,10 +242,12 @@ class NodeDetails extends React.Component {
|
||||
<div className="node-details-content-section" key={table.id}>
|
||||
<div className="node-details-content-section-header">
|
||||
{table.label && table.label.length > 0 && table.label}
|
||||
{table.truncationCount > 0 && <span
|
||||
className="node-details-content-section-header-warning">
|
||||
<Warning text={getTruncationText(table.truncationCount)} />
|
||||
</span>}
|
||||
{table.truncationCount > 0 &&
|
||||
<span
|
||||
className="node-details-content-section-header-warning">
|
||||
<Warning text={getTruncationText(table.truncationCount)} />
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
{this.renderTable(table)}
|
||||
</div>
|
||||
@@ -263,14 +276,16 @@ class NodeDetails extends React.Component {
|
||||
if (isGenericTable(table)) {
|
||||
return (
|
||||
<NodeDetailsGenericTable
|
||||
rows={table.rows} columns={table.columns}
|
||||
rows={table.rows}
|
||||
columns={table.columns}
|
||||
matches={nodeMatches.get('tables')}
|
||||
/>
|
||||
);
|
||||
} else if (isPropertyList(table)) {
|
||||
return (
|
||||
<NodeDetailsPropertyList
|
||||
rows={table.rows} controls={table.controls}
|
||||
rows={table.rows}
|
||||
controls={table.controls}
|
||||
matches={nodeMatches.get('property-lists')}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -58,8 +58,7 @@ describe('NodeDetailsTable', () => {
|
||||
// form columnIndex + n * columns.length, where n >= 0. Therefore we take only the values
|
||||
// at the index which divided by columns.length gives a reminder columnIndex.
|
||||
const filteredValues = values.filter((element, index) =>
|
||||
index % columns.length === columnIndex
|
||||
);
|
||||
index % columns.length === columnIndex);
|
||||
// Array comparison
|
||||
expect(filteredValues).toEqual(expectedValues);
|
||||
}
|
||||
@@ -67,21 +66,21 @@ describe('NodeDetailsTable', () => {
|
||||
function clickColumn(title) {
|
||||
const node = TestUtils.scryRenderedDOMComponentsWithTag(component, 'td')
|
||||
.find(d => d.title === title);
|
||||
TestUtils.Simulate.click(node);
|
||||
TestUtils.Simulate.click(node.children[0]);
|
||||
}
|
||||
|
||||
describe('kubernetes_ip', () => {
|
||||
it('sorts by column', () => {
|
||||
component = TestUtils.renderIntoDocument(
|
||||
component = TestUtils.renderIntoDocument((
|
||||
<Provider store={configureStore()}>
|
||||
<NodeDetailsTable
|
||||
columns={columns}
|
||||
sortedBy="kubernetes_ip"
|
||||
nodeIdKey="id"
|
||||
nodes={nodes}
|
||||
/>
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
));
|
||||
|
||||
matchColumnValues('kubernetes_ip', [
|
||||
'10.44.253.255',
|
||||
@@ -108,16 +107,16 @@ describe('NodeDetailsTable', () => {
|
||||
|
||||
describe('kubernetes_namespace', () => {
|
||||
it('sorts by column', () => {
|
||||
component = TestUtils.renderIntoDocument(
|
||||
component = TestUtils.renderIntoDocument((
|
||||
<Provider store={configureStore()}>
|
||||
<NodeDetailsTable
|
||||
columns={columns}
|
||||
sortedBy="kubernetes_namespace"
|
||||
nodeIdKey="id"
|
||||
nodes={nodes}
|
||||
/>
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
));
|
||||
|
||||
matchColumnValues('kubernetes_namespace', ['00000', '1111', '12', '5']);
|
||||
clickColumn('Namespace');
|
||||
|
||||
@@ -3,7 +3,9 @@ import { sortBy } from 'lodash';
|
||||
|
||||
import NodeDetailsControlButton from './node-details-control-button';
|
||||
|
||||
export default function NodeDetailsControls({controls, error, nodeId, pending}) {
|
||||
export default function NodeDetailsControls({
|
||||
controls, error, nodeId, pending
|
||||
}) {
|
||||
let spinnerClassName = 'fa fa-circle-o-notch fa-spin';
|
||||
if (pending) {
|
||||
spinnerClassName += ' node-details-controls-spinner';
|
||||
@@ -13,13 +15,18 @@ export default function NodeDetailsControls({controls, error, nodeId, pending})
|
||||
|
||||
return (
|
||||
<div className="node-details-controls">
|
||||
{error && <div className="node-details-controls-error" title={error}>
|
||||
<span className="node-details-controls-error-icon fa fa-warning" />
|
||||
<span className="node-details-controls-error-messages">{error}</span>
|
||||
</div>}
|
||||
{error &&
|
||||
<div className="node-details-controls-error" title={error}>
|
||||
<span className="node-details-controls-error-icon fa fa-warning" />
|
||||
<span className="node-details-controls-error-messages">{error}</span>
|
||||
</div>
|
||||
}
|
||||
<span className="node-details-controls-buttons">
|
||||
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
|
||||
nodeId={nodeId} control={control} pending={pending} key={control.id} />)}
|
||||
{sortBy(controls, 'rank').map(control => (<NodeDetailsControlButton
|
||||
nodeId={nodeId}
|
||||
control={control}
|
||||
pending={pending}
|
||||
key={control.id} />))}
|
||||
</span>
|
||||
{controls && <span title="Applying..." className={spinnerClassName} />}
|
||||
</div>
|
||||
|
||||
@@ -63,8 +63,7 @@ export default class NodeDetailsGenericTable extends React.Component {
|
||||
// expanded if any of them match the search query; otherwise hide them.
|
||||
if (this.state.limit > 0 && rows.length > this.state.limit) {
|
||||
const hasHiddenMatch = rows.slice(this.state.limit).some(row =>
|
||||
columns.some(column => matches.has(genericTableEntryKey(row, column)))
|
||||
);
|
||||
columns.some(column => matches.has(genericTableEntryKey(row, column))));
|
||||
if (!hasHiddenMatch) {
|
||||
notShown = rows.length - NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT;
|
||||
rows = rows.slice(0, this.state.limit);
|
||||
@@ -92,7 +91,9 @@ export default class NodeDetailsGenericTable extends React.Component {
|
||||
return (
|
||||
<td
|
||||
className="node-details-generic-table-value truncate"
|
||||
title={value} key={column.id} style={styles[index]}>
|
||||
title={value}
|
||||
key={column.id}
|
||||
style={styles[index]}>
|
||||
<MatchedText text={value} match={match} />
|
||||
</td>
|
||||
);
|
||||
@@ -102,8 +103,10 @@ export default class NodeDetailsGenericTable extends React.Component {
|
||||
</tbody>
|
||||
</table>
|
||||
<ShowMore
|
||||
handleClick={this.handleLimitClick} collection={this.props.rows}
|
||||
expanded={expanded} notShown={notShown}
|
||||
handleClick={this.handleLimitClick}
|
||||
collection={this.props.rows}
|
||||
expanded={expanded}
|
||||
notShown={notShown}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -10,8 +10,12 @@ function NodeDetailsHealthItem(props) {
|
||||
{!props.valueEmpty && <div className="node-details-health-item-value" style={labelStyle}>{formatMetric(props.value, props)}</div>}
|
||||
<div className="node-details-health-item-sparkline">
|
||||
<Sparkline
|
||||
data={props.samples} max={props.max} format={props.format}
|
||||
first={props.first} last={props.last} hoverColor={props.metricColor}
|
||||
data={props.samples}
|
||||
max={props.max}
|
||||
format={props.format}
|
||||
first={props.first}
|
||||
last={props.last}
|
||||
hoverColor={props.metricColor}
|
||||
hovered={props.hovered}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -38,7 +38,6 @@ export function appendTime(url, time) {
|
||||
}
|
||||
|
||||
class NodeDetailsHealthLinkItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@@ -63,7 +62,9 @@ class NodeDetailsHealthLinkItem extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { id, url, pausedAt, ...props } = this.props;
|
||||
const {
|
||||
id, url, pausedAt, ...props
|
||||
} = this.props;
|
||||
const metricColor = getMetricColor(id);
|
||||
const labelColor = this.state.hovered && !props.valueEmpty && darkenColor(metricColor);
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import ShowMore from '../show-more';
|
||||
import NodeDetailsHealthLinkItem from './node-details-health-link-item';
|
||||
|
||||
export default class NodeDetailsHealth extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
@@ -39,22 +38,25 @@ export default class NodeDetailsHealth extends React.Component {
|
||||
return (
|
||||
<div className="node-details-health" style={{ justifyContent: 'space-around' }}>
|
||||
<div className="node-details-health-wrapper">
|
||||
{shownWithData.map(item => <NodeDetailsHealthLinkItem
|
||||
{shownWithData.map(item => (<NodeDetailsHealthLinkItem
|
||||
{...item}
|
||||
key={item.id}
|
||||
topologyId={topologyId}
|
||||
/>)}
|
||||
/>))}
|
||||
</div>
|
||||
<div className="node-details-health-wrapper">
|
||||
{shownEmpty.map(item => <NodeDetailsHealthLinkItem
|
||||
{shownEmpty.map(item => (<NodeDetailsHealthLinkItem
|
||||
{...item}
|
||||
key={item.id}
|
||||
topologyId={topologyId}
|
||||
/>)}
|
||||
/>))}
|
||||
</div>
|
||||
<ShowMore
|
||||
handleClick={this.handleClickMore} collection={metrics}
|
||||
expanded={this.state.expanded} notShown={notShown} hideNumber={this.state.expanded}
|
||||
handleClick={this.handleClickMore}
|
||||
collection={metrics}
|
||||
expanded={this.state.expanded}
|
||||
notShown={notShown}
|
||||
hideNumber={this.state.expanded}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -90,11 +90,11 @@ class NodeDetailsImageStatus extends React.PureComponent {
|
||||
Container Image Status
|
||||
{containers &&
|
||||
<div>
|
||||
<a
|
||||
<button
|
||||
onClick={this.handleServiceClick}
|
||||
className="node-details-table-node-link">
|
||||
View in Deploy
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -57,8 +57,10 @@ class NodeDetailsInfo extends React.Component {
|
||||
);
|
||||
})}
|
||||
<ShowMore
|
||||
handleClick={this.handleClickMore} collection={this.props.rows}
|
||||
expanded={this.state.expanded} notShown={notShown} />
|
||||
handleClick={this.handleClickMore}
|
||||
collection={this.props.rows}
|
||||
expanded={this.state.expanded}
|
||||
notShown={notShown} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@ import ShowMore from '../show-more';
|
||||
|
||||
const Controls = controls => (
|
||||
<div className="node-details-property-list-controls">
|
||||
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
|
||||
nodeId={control.nodeId} control={control} key={control.id} />)}
|
||||
{sortBy(controls, 'rank').map(control => (<NodeDetailsControlButton
|
||||
nodeId={control.nodeId}
|
||||
control={control}
|
||||
key={control.id} />))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -30,7 +32,7 @@ export default class NodeDetailsPropertyList extends React.Component {
|
||||
|
||||
render() {
|
||||
const { controls, matches = makeMap() } = this.props;
|
||||
let rows = this.props.rows;
|
||||
let { rows } = this.props;
|
||||
let notShown = 0;
|
||||
const limited = rows && this.state.limit > 0 && rows.length > this.state.limit;
|
||||
const expanded = this.state.limit === 0;
|
||||
@@ -50,7 +52,8 @@ export default class NodeDetailsPropertyList extends React.Component {
|
||||
<div className="node-details-property-list-field" key={field.id}>
|
||||
<div
|
||||
className="node-details-property-list-field-label truncate"
|
||||
title={field.entries.label} key={field.id}>
|
||||
title={field.entries.label}
|
||||
key={field.id}>
|
||||
{field.entries.label}
|
||||
</div>
|
||||
<div
|
||||
@@ -61,8 +64,10 @@ export default class NodeDetailsPropertyList extends React.Component {
|
||||
</div>
|
||||
))}
|
||||
<ShowMore
|
||||
handleClick={this.handleLimitClick} collection={this.props.rows}
|
||||
expanded={expanded} notShown={notShown} />
|
||||
handleClick={this.handleLimitClick}
|
||||
collection={this.props.rows}
|
||||
expanded={expanded}
|
||||
notShown={notShown} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,10 @@ class NodeDetailsRelativesLink extends React.Component {
|
||||
const title = `View in ${this.props.topologyId}: ${this.props.label}`;
|
||||
return (
|
||||
<span
|
||||
className="node-details-relatives-link" title={title}
|
||||
onClick={this.handleClick} ref={this.saveNodeRef}>
|
||||
className="node-details-relatives-link"
|
||||
title={title}
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveNodeRef}>
|
||||
<MatchedText text={this.props.label} match={this.props.match} />
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,6 @@ import { NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT } from '../../constants/limits';
|
||||
import NodeDetailsRelativesLink from './node-details-relatives-link';
|
||||
|
||||
export default class NodeDetailsRelatives extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.state = {
|
||||
@@ -39,11 +38,13 @@ export default class NodeDetailsRelatives extends React.Component {
|
||||
key={relative.id}
|
||||
match={matches.get(relative.id)}
|
||||
{...relative} />))}
|
||||
{showLimitAction && <span
|
||||
className="node-details-relatives-more"
|
||||
onClick={this.handleLimitClick}>
|
||||
{limitActionText}
|
||||
</span>}
|
||||
{showLimitAction &&
|
||||
<span
|
||||
className="node-details-relatives-more"
|
||||
onClick={this.handleLimitClick}>
|
||||
{limitActionText}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,14 +38,14 @@ export default class NodeDetailsTableHeaders extends React.Component {
|
||||
NODE_DETAILS_TABLE_XS_LABEL[header.id] : header.label;
|
||||
|
||||
return (
|
||||
<td
|
||||
className={headerClasses.join(' ')} style={style} onClick={onClick}
|
||||
title={header.label} key={header.id}>
|
||||
{isSortedAsc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
|
||||
{isSortedDesc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
|
||||
{label}
|
||||
<td className={headerClasses.join(' ')} style={style} title={header.label} key={header.id}>
|
||||
<div className="node-details-table-header-sortable" onClick={onClick}>
|
||||
{isSortedAsc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
|
||||
{isSortedDesc
|
||||
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
|
||||
{label}
|
||||
</div>
|
||||
</td>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -38,8 +38,10 @@ class NodeDetailsTableNodeLink extends React.Component {
|
||||
if (linkable) {
|
||||
return (
|
||||
<span
|
||||
className="node-details-table-node-link" title={title}
|
||||
ref={this.saveNodeRef} onClick={this.handleClick}
|
||||
className="node-details-table-node-link"
|
||||
title={title}
|
||||
ref={this.saveNodeRef}
|
||||
onClick={this.handleClick}
|
||||
{...dismissRowClickProps}
|
||||
>
|
||||
{label}
|
||||
|
||||
@@ -17,7 +17,9 @@ class NodeDetailsTableNodeMetricLink extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { url, style, value, valueEmpty } = this.props;
|
||||
const {
|
||||
url, style, value, valueEmpty
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<td
|
||||
|
||||
@@ -66,20 +66,22 @@ function renderValues(node, columns = [], columnStyles = [], timestamp = null, t
|
||||
style={style}
|
||||
key={field.id}>
|
||||
{intersperse(field.relatives.map(relative =>
|
||||
<NodeDetailsTableNodeLink
|
||||
(<NodeDetailsTableNodeLink
|
||||
key={relative.id}
|
||||
linkable
|
||||
nodeId={relative.id}
|
||||
{...relative}
|
||||
/>
|
||||
), ' ')}
|
||||
/>)), ' ')}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
// valueType === 'metrics'
|
||||
return (
|
||||
<NodeDetailsTableNodeMetricLink
|
||||
style={style} key={field.id} topologyId={topologyId} {...field} />
|
||||
style={style}
|
||||
key={field.id}
|
||||
topologyId={topologyId}
|
||||
{...field} />
|
||||
);
|
||||
}
|
||||
// empty cell to complete the row for proper hover
|
||||
@@ -155,7 +157,9 @@ export default class NodeDetailsTableRow extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { node, nodeIdKey, topologyId, columns, onClick, colStyles, timestamp } = this.props;
|
||||
const {
|
||||
node, nodeIdKey, topologyId, columns, onClick, colStyles, timestamp
|
||||
} = this.props;
|
||||
const [firstColumnStyle, ...columnStyles] = colStyles;
|
||||
const values = renderValues(node, columns, columnStyles, timestamp, topologyId);
|
||||
const nodeId = node[nodeIdKey];
|
||||
|
||||
@@ -202,8 +202,10 @@ class NodeDetailsTable extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nodeIdKey, columns, topologyId, onClickRow,
|
||||
onMouseEnter, onMouseLeave, timestamp } = this.props;
|
||||
const {
|
||||
nodeIdKey, columns, topologyId, onClickRow,
|
||||
onMouseEnter, onMouseLeave, timestamp
|
||||
} = this.props;
|
||||
|
||||
const sortedBy = this.state.sortedBy || getDefaultSortedBy(columns, this.props.nodes);
|
||||
const sortedByHeader = this.getColumnHeaders().find(h => h.id === sortedBy);
|
||||
@@ -294,7 +296,7 @@ class NodeDetailsTable extends React.Component {
|
||||
|
||||
|
||||
NodeDetailsTable.defaultProps = {
|
||||
nodeIdKey: 'id', // key to identify a node in a row (used for topology links)
|
||||
nodeIdKey: 'id', // key to identify a node in a row (used for topology links)
|
||||
limit: NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT,
|
||||
onSortChange: () => {},
|
||||
sortedDesc: null,
|
||||
|
||||
@@ -42,7 +42,8 @@ class NodesResources extends React.Component {
|
||||
<div className="nodes-resources">
|
||||
<ZoomableCanvas
|
||||
onClick={this.handleMouseClick}
|
||||
fixVertical boundContent={CONTENT_COVERING}
|
||||
fixVertical
|
||||
boundContent={CONTENT_COVERING}
|
||||
limitsSelector={resourcesLimitsSelector}
|
||||
zoomStateSelector={resourcesZoomStateSelector}>
|
||||
{transform => this.renderLayers(transform)}
|
||||
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
|
||||
class NodesResourcesLayer extends React.Component {
|
||||
render() {
|
||||
const { layerVerticalPosition, topologyId, transform, layoutNodes } = this.props;
|
||||
const {
|
||||
layerVerticalPosition, topologyId, transform, layoutNodes
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<g className="node-resources-layer">
|
||||
@@ -50,6 +52,4 @@ function mapStateToProps(state, props) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(NodesResourcesLayer);
|
||||
export default connect(mapStateToProps)(NodesResourcesLayer);
|
||||
|
||||
@@ -3,8 +3,10 @@ import React from 'react';
|
||||
|
||||
export default class NodeResourcesMetricBoxInfo extends React.Component {
|
||||
humanizedMetricInfo() {
|
||||
const { humanizedTotalCapacity, humanizedAbsoluteConsumption,
|
||||
humanizedRelativeConsumption, showCapacity, format } = this.props.metricSummary.toJS();
|
||||
const {
|
||||
humanizedTotalCapacity, humanizedAbsoluteConsumption,
|
||||
humanizedRelativeConsumption, showCapacity, format
|
||||
} = this.props.metricSummary.toJS();
|
||||
const showExtendedInfo = showCapacity && format !== 'percent';
|
||||
|
||||
return (
|
||||
@@ -12,9 +14,11 @@ export default class NodeResourcesMetricBoxInfo extends React.Component {
|
||||
<strong>
|
||||
{showExtendedInfo ? humanizedRelativeConsumption : humanizedAbsoluteConsumption}
|
||||
</strong> used
|
||||
{showExtendedInfo && <i>{' - '}
|
||||
({humanizedAbsoluteConsumption} / <strong>{humanizedTotalCapacity}</strong>)
|
||||
</i>}
|
||||
{showExtendedInfo &&
|
||||
<i>
|
||||
{' - '}({humanizedAbsoluteConsumption} / <strong>{humanizedTotalCapacity}</strong>)
|
||||
</i>
|
||||
}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ import {
|
||||
// down to this component, so a lot of stuff gets rerendered/recalculated on every zoom action.
|
||||
// On the other hand, this enables us to easily leave out the nodes that are not in the viewport.
|
||||
const transformedDimensions = (props) => {
|
||||
const { width, height, x, y } = applyTransform(props.transform, props);
|
||||
const {
|
||||
width, height, x, y
|
||||
} = applyTransform(props.transform, props);
|
||||
|
||||
// Trim the beginning of the resource box just after the layer topology
|
||||
// name to the left and the viewport width to the right. That enables us
|
||||
@@ -79,7 +81,9 @@ class NodeResourcesMetricBox extends React.Component {
|
||||
}
|
||||
|
||||
defaultRectProps(relativeHeight = 1) {
|
||||
const { x, y, width, height } = this.state;
|
||||
const {
|
||||
x, y, width, height
|
||||
} = this.state;
|
||||
const translateY = height * (1 - relativeHeight);
|
||||
return {
|
||||
transform: `translate(0, ${translateY})`,
|
||||
@@ -94,7 +98,9 @@ class NodeResourcesMetricBox extends React.Component {
|
||||
|
||||
render() {
|
||||
const { x, y, width } = this.state;
|
||||
const { id, selectedNodeId, label, color, metricSummary } = this.props;
|
||||
const {
|
||||
id, selectedNodeId, label, color, metricSummary
|
||||
} = this.props;
|
||||
const { showCapacity, relativeConsumption, type } = metricSummary.toJS();
|
||||
const opacity = (selectedNodeId && selectedNodeId !== id) ? 0.35 : 1;
|
||||
|
||||
@@ -111,8 +117,10 @@ class NodeResourcesMetricBox extends React.Component {
|
||||
|
||||
return (
|
||||
<g
|
||||
className="node-resources-metric-box" style={{ opacity }}
|
||||
onClick={this.handleClick} ref={this.saveNodeRef}>
|
||||
className="node-resources-metric-box"
|
||||
style={{ opacity }}
|
||||
onClick={this.handleClick}
|
||||
ref={this.saveNodeRef}>
|
||||
<title>{label} - {type} usage at {resourceUsageTooltipInfo}</title>
|
||||
{showCapacity && <rect className="frame" {...this.defaultRectProps()} />}
|
||||
<rect className="bar" fill={color} {...this.defaultRectProps(relativeConsumption)} />
|
||||
|
||||
@@ -52,8 +52,10 @@ class Nodes extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { topologiesLoaded, nodesLoaded, topologies, currentTopology, isGraphViewMode,
|
||||
isTableViewMode, isResourceViewMode } = this.props;
|
||||
const {
|
||||
topologiesLoaded, nodesLoaded, topologies, currentTopology, isGraphViewMode,
|
||||
isTableViewMode, isResourceViewMode
|
||||
} = this.props;
|
||||
|
||||
// TODO: Rename view mode components.
|
||||
return (
|
||||
@@ -92,6 +94,4 @@ function mapStateToProps(state) {
|
||||
}
|
||||
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(Nodes);
|
||||
export default connect(mapStateToProps)(Nodes);
|
||||
|
||||
@@ -5,7 +5,9 @@ import classNames from 'classnames';
|
||||
import Tooltip from './tooltip';
|
||||
|
||||
|
||||
const Plugin = ({id, label, description, status}) => {
|
||||
const Plugin = ({
|
||||
id, label, description, status
|
||||
}) => {
|
||||
const error = status !== 'ok';
|
||||
const className = classNames({ error });
|
||||
const tip = (<span>Description: {description}<br />Status: {status}</span>);
|
||||
@@ -44,6 +46,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(Plugins);
|
||||
export default connect(mapStateToProps)(Plugins);
|
||||
|
||||
@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
|
||||
import { unpinSearch } from '../actions/app-actions';
|
||||
|
||||
class SearchItem extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
|
||||
@@ -30,8 +30,7 @@ function getHint(nodes) {
|
||||
|
||||
const node = nodes.filter(n => !n.get('pseudo') && n.has('metadata')).last();
|
||||
if (node) {
|
||||
label = shortenHintLabel(node.get('label'))
|
||||
.split('.')[0];
|
||||
[label] = shortenHintLabel(node.get('label')).split('.');
|
||||
if (node.get('metadata')) {
|
||||
const metadataField = node.get('metadata').first();
|
||||
metadataLabel = shortenHintLabel(slugify(metadataField.get('label')))
|
||||
@@ -46,7 +45,6 @@ function getHint(nodes) {
|
||||
|
||||
|
||||
class Search extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
@@ -126,8 +124,10 @@ class Search extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { nodes, pinnedSearches, searchFocused, searchMatchCountByTopology,
|
||||
isResourceViewMode, searchQuery, topologiesLoaded, inputId = 'search' } = this.props;
|
||||
const {
|
||||
nodes, pinnedSearches, searchFocused, searchMatchCountByTopology,
|
||||
isResourceViewMode, searchQuery, topologiesLoaded, inputId = 'search'
|
||||
} = this.props;
|
||||
const hidden = !topologiesLoaded || isResourceViewMode;
|
||||
const disabled = this.props.isTopologyNodeCountZero && !hidden;
|
||||
const matchCount = searchMatchCountByTopology
|
||||
@@ -152,22 +152,30 @@ class Search extends React.Component {
|
||||
{showPinnedSearches && pinnedSearches.toIndexedSeq()
|
||||
.map(query => <SearchItem query={query} key={query} />)}
|
||||
<input
|
||||
className="search-input-field" type="text" id={inputId}
|
||||
value={value} onChange={this.handleChange} onKeyUp={this.handleKeyUp}
|
||||
onFocus={this.handleFocus} onBlur={this.handleBlur}
|
||||
disabled={disabled} ref={this.saveQueryInputRef} />
|
||||
className="search-input-field"
|
||||
type="text"
|
||||
id={inputId}
|
||||
value={value}
|
||||
onChange={this.handleChange}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
disabled={disabled}
|
||||
ref={this.saveQueryInputRef} />
|
||||
</div>
|
||||
<div className="search-label">
|
||||
<i className="fa fa-search search-label-icon" />
|
||||
<label className="search-label-hint" htmlFor={inputId}>
|
||||
<span className="search-label-hint" htmlFor={inputId}>
|
||||
Search
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
{!showPinnedSearches && <div className="search-hint">
|
||||
{getHint(nodes)} <span
|
||||
className="search-help-link fa fa-question-circle"
|
||||
onMouseDown={this.props.toggleHelp} />
|
||||
</div>}
|
||||
{!showPinnedSearches &&
|
||||
<div className="search-hint">
|
||||
{getHint(nodes)} <span
|
||||
className="search-help-link fa fa-question-circle"
|
||||
onMouseDown={this.props.toggleHelp} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -188,5 +196,7 @@ export default connect(
|
||||
searchQuery: state.get('searchQuery'),
|
||||
searchMatchCountByTopology: searchMatchCountByTopologySelector(state),
|
||||
}),
|
||||
{ blurSearch, doSearch, focusSearch, pinSearch, toggleHelp }
|
||||
{
|
||||
blurSearch, doSearch, focusSearch, pinSearch, toggleHelp
|
||||
}
|
||||
)(Search);
|
||||
|
||||
@@ -12,7 +12,9 @@ export default class ShowMore extends React.PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { collection, notShown, expanded, hideNumber } = this.props;
|
||||
const {
|
||||
collection, notShown, expanded, hideNumber
|
||||
} = this.props;
|
||||
const showLimitAction = collection && (expanded || notShown > 0);
|
||||
const limitActionText = !hideNumber && !expanded && notShown > 0 ? `+${notShown}` : '';
|
||||
const limitActionIcon = !expanded && notShown > 0 ? 'fa fa-caret-down' : 'fa fa-caret-up';
|
||||
|
||||
@@ -33,12 +33,12 @@ export default class Sparkline extends React.Component {
|
||||
|
||||
this.x.range([MARGIN, this.props.width - circleSpace]);
|
||||
this.y.range([this.props.height - circleSpace, circleSpace]);
|
||||
this.line.curve(this.props.curve);
|
||||
this.line.curve(curveLinear);
|
||||
}
|
||||
|
||||
getGraphData() {
|
||||
// data is of shape [{date, value}, ...] and is sorted by date (ASC)
|
||||
let data = this.props.data;
|
||||
let { data } = this.props;
|
||||
|
||||
this.initRanges(true);
|
||||
|
||||
@@ -75,7 +75,9 @@ export default class Sparkline extends React.Component {
|
||||
const title = `Last ${Math.round((lastDate - firstDate) / 1000)} seconds, ` +
|
||||
`${data.length} samples, min: ${min}, max: ${max}, mean: ${mean}`;
|
||||
|
||||
return {title, lastX, lastY, data};
|
||||
return {
|
||||
title, lastX, lastY, data
|
||||
};
|
||||
}
|
||||
|
||||
getEmptyGraphData() {
|
||||
@@ -113,13 +115,21 @@ export default class Sparkline extends React.Component {
|
||||
<div title={graph.title}>
|
||||
<svg width={this.props.width} height={this.props.height}>
|
||||
<path
|
||||
className="sparkline" fill="none" stroke={strokeColor}
|
||||
strokeWidth={strokeWidth} strokeDasharray={strokeDasharray}
|
||||
className="sparkline"
|
||||
fill="none"
|
||||
stroke={strokeColor}
|
||||
strokeWidth={strokeWidth}
|
||||
strokeDasharray={strokeDasharray}
|
||||
d={this.line(graph.data)}
|
||||
/>
|
||||
{hasData && <circle
|
||||
className="sparkcircle" cx={graph.lastX} cy={graph.lastY} fill={circleColor}
|
||||
fillOpacity={fillOpacity} stroke="none" r={radius}
|
||||
className="sparkcircle"
|
||||
cx={graph.lastX}
|
||||
cy={graph.lastY}
|
||||
fill={circleColor}
|
||||
fillOpacity={fillOpacity}
|
||||
stroke="none"
|
||||
r={radius}
|
||||
/>}
|
||||
</svg>
|
||||
</div>
|
||||
@@ -128,7 +138,14 @@ export default class Sparkline extends React.Component {
|
||||
}
|
||||
|
||||
Sparkline.propTypes = {
|
||||
data: PropTypes.arrayOf(PropTypes.object)
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
strokeColor: PropTypes.string,
|
||||
strokeWidth: PropTypes.number,
|
||||
hoverColor: PropTypes.string,
|
||||
circleRadius: PropTypes.number,
|
||||
hovered: PropTypes.bool,
|
||||
data: PropTypes.arrayOf(PropTypes.object),
|
||||
};
|
||||
|
||||
Sparkline.defaultProps = {
|
||||
@@ -137,7 +154,6 @@ Sparkline.defaultProps = {
|
||||
strokeColor: '#7d7da8',
|
||||
strokeWidth: 0.5,
|
||||
hoverColor: '#7d7da8',
|
||||
curve: curveLinear,
|
||||
circleRadius: 1.75,
|
||||
hovered: false,
|
||||
data: [],
|
||||
|
||||
@@ -6,7 +6,9 @@ import { isPausedSelector } from '../selectors/time-travel';
|
||||
|
||||
class Status extends React.Component {
|
||||
render() {
|
||||
const { errorUrl, topologiesLoaded, filteredNodeCount, topology, websocketClosed } = this.props;
|
||||
const {
|
||||
errorUrl, topologiesLoaded, filteredNodeCount, topology, websocketClosed
|
||||
} = this.props;
|
||||
|
||||
let title = '';
|
||||
let text = 'Trying to reconnect...';
|
||||
@@ -54,6 +56,4 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(
|
||||
mapStateToProps
|
||||
)(Status);
|
||||
export default connect(mapStateToProps)(Status);
|
||||
|
||||
@@ -7,14 +7,15 @@ import { receiveControlPipeFromParams, hitEsc } from '../actions/app-actions';
|
||||
const ESC_KEY_CODE = 27;
|
||||
|
||||
class TerminalApp extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const paramString = window.location.hash.split('/').pop();
|
||||
const params = JSON.parse(decodeURIComponent(paramString));
|
||||
this.props.receiveControlPipeFromParams(params.pipe.id, params.pipe.raw,
|
||||
params.pipe.resizeTtyControl);
|
||||
this.props.receiveControlPipeFromParams(
|
||||
params.pipe.id, params.pipe.raw,
|
||||
params.pipe.resizeTtyControl
|
||||
);
|
||||
|
||||
this.state = {
|
||||
title: params.title,
|
||||
|
||||
@@ -132,7 +132,9 @@ class Terminal extends React.Component {
|
||||
this.createWebsocket(term);
|
||||
} else {
|
||||
this.reconnectTimeout = setTimeout(
|
||||
this.createWebsocket.bind(this, term), reconnectTimerInterval);
|
||||
this.createWebsocket.bind(this, term),
|
||||
reconnectTimerInterval
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -291,7 +293,8 @@ class Terminal extends React.Component {
|
||||
Pop out
|
||||
</span>
|
||||
<span
|
||||
title="Close" className="terminal-header-tools-item-icon fa fa-close"
|
||||
title="Close"
|
||||
className="terminal-header-tools-item-icon fa fa-close"
|
||||
onClick={this.handleCloseClick} />
|
||||
</div>
|
||||
{this.getControlStatusIcon()}
|
||||
@@ -379,8 +382,7 @@ class Terminal extends React.Component {
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
const controlStatus = state.get('controlPipes').find(pipe =>
|
||||
pipe.get('nodeId') === ownProps.pipe.get('nodeId')
|
||||
);
|
||||
pipe.get('nodeId') === ownProps.pipe.get('nodeId'));
|
||||
return { controlStatus };
|
||||
}
|
||||
|
||||
|
||||
@@ -61,8 +61,10 @@ class TimeControl extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { showingTimeTravel, pausedAt, timeTravelTransitioning, topologiesLoaded,
|
||||
hasHistoricReports } = this.props;
|
||||
const {
|
||||
showingTimeTravel, pausedAt, timeTravelTransitioning, topologiesLoaded,
|
||||
hasHistoricReports
|
||||
} = this.props;
|
||||
|
||||
const isPausedNow = pausedAt && !showingTimeTravel;
|
||||
const isTimeTravelling = showingTimeTravel;
|
||||
@@ -92,24 +94,27 @@ class TimeControl extends React.Component {
|
||||
{isPausedNow && <span className="fa fa-pause" />}
|
||||
<span className="label">{isPausedNow ? 'Paused' : 'Pause'}</span>
|
||||
</span>
|
||||
{hasHistoricReports && <span
|
||||
className={className(isTimeTravelling)}
|
||||
onClick={this.handleTravelClick}
|
||||
title="Travel back in time">
|
||||
{isTimeTravelling && <span className="fa fa-clock-o" />}
|
||||
<span className="label">Time Travel</span>
|
||||
</span>}
|
||||
{hasHistoricReports &&
|
||||
<span
|
||||
className={className(isTimeTravelling)}
|
||||
onClick={this.handleTravelClick}
|
||||
title="Travel back in time">
|
||||
{isTimeTravelling && <span className="fa fa-clock-o" />}
|
||||
<span className="label">Time Travel</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{(isPausedNow || isTimeTravelling) && <span
|
||||
className="time-control-info"
|
||||
title={moment(pausedAt).toISOString()}>
|
||||
Showing state from {moment(pausedAt).fromNow()}
|
||||
</span>}
|
||||
{isRunningNow && timeTravelTransitioning && <span
|
||||
className="time-control-info">
|
||||
Resuming the live state
|
||||
</span>}
|
||||
{(isPausedNow || isTimeTravelling) &&
|
||||
<span
|
||||
className="time-control-info"
|
||||
title={moment(pausedAt).toISOString()}>
|
||||
Showing state from {moment(pausedAt).fromNow()}
|
||||
</span>
|
||||
}
|
||||
{isRunningNow && timeTravelTransitioning &&
|
||||
<span className="time-control-info">Resuming the live state</span>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,7 +145,20 @@ const DisabledRange = styled.rect`
|
||||
fill-opacity: 0.15;
|
||||
`;
|
||||
|
||||
const TimestampLabel = styled.a`
|
||||
const ShallowButton = styled.button`
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
color: ${props => props.theme.colors.primary.lavender};
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
|
||||
&:hover {
|
||||
color: ${props => props.theme.colors.primary.charcoal};
|
||||
}
|
||||
`;
|
||||
|
||||
const TimestampLabel = ShallowButton.extend`
|
||||
margin-left: 2px;
|
||||
padding: 3px;
|
||||
|
||||
@@ -155,7 +168,7 @@ const TimestampLabel = styled.a`
|
||||
}
|
||||
`;
|
||||
|
||||
const TimelinePanButton = styled.a`
|
||||
const TimelinePanButton = ShallowButton.extend`
|
||||
pointer-events: all;
|
||||
padding: 2px;
|
||||
`;
|
||||
@@ -236,7 +249,9 @@ export default class TimeTravelComponent extends React.Component {
|
||||
|
||||
this.instantUpdateTimestamp = this.instantUpdateTimestamp.bind(this);
|
||||
this.debouncedUpdateTimestamp = debounce(
|
||||
this.instantUpdateTimestamp.bind(this), TIMELINE_DEBOUNCE_INTERVAL);
|
||||
this.instantUpdateTimestamp.bind(this),
|
||||
TIMELINE_DEBOUNCE_INTERVAL
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@@ -498,7 +513,9 @@ export default class TimeTravelComponent extends React.Component {
|
||||
<rect
|
||||
className="tooltip-container"
|
||||
transform={`translate(${-width / 2}, 0)`}
|
||||
width={width} height={height} fillOpacity={0}
|
||||
width={width}
|
||||
height={height}
|
||||
fillOpacity={0}
|
||||
/>
|
||||
{this.renderDisabledShadow(timelineTransform)}
|
||||
<g className="ticks" transform="translate(0, 1)">
|
||||
|
||||
@@ -20,7 +20,6 @@ function basicTopologyInfo(topology, searchMatchCount) {
|
||||
}
|
||||
|
||||
class Topologies extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onTopologyClick = this.onTopologyClick.bind(this);
|
||||
@@ -48,7 +47,10 @@ class Topologies extends React.Component {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={className} title={title} key={topologyId} rel={topologyId}
|
||||
className={className}
|
||||
title={title}
|
||||
key={topologyId}
|
||||
rel={topologyId}
|
||||
onClick={ev => this.onTopologyClick(ev, subTopology)}>
|
||||
<div className="topologies-sub-item-label">
|
||||
{subTopology.get('name')}
|
||||
@@ -71,7 +73,9 @@ class Topologies extends React.Component {
|
||||
return (
|
||||
<div className="topologies-item" key={topologyId}>
|
||||
<div
|
||||
className={className} title={title} rel={topologyId}
|
||||
className={className}
|
||||
title={title}
|
||||
rel={topologyId}
|
||||
onClick={ev => this.onTopologyClick(ev, topology)}>
|
||||
<div className="topologies-item-label">
|
||||
{topology.get('name')}
|
||||
@@ -88,9 +92,7 @@ class Topologies extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div className="topologies">
|
||||
{this.props.currentTopology && this.props.topologies.map(
|
||||
topology => this.renderTopology(topology)
|
||||
)}
|
||||
{this.props.currentTopology && this.props.topologies.map(t => this.renderTopology(t))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class TopologyOptionAction extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.onClick = this.onClick.bind(this);
|
||||
|
||||
@@ -129,8 +129,7 @@ class TopologyOptions extends React.Component {
|
||||
const { options } = this.props;
|
||||
return (
|
||||
<div className="topology-options">
|
||||
{options && options.toIndexedSeq().map(
|
||||
option => this.renderOption(option))}
|
||||
{options && options.toIndexedSeq().map(option => this.renderOption(option))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ class DebugMenu extends React.Component {
|
||||
</a>
|
||||
</div>
|
||||
<div className="troubleshooting-menu-item">
|
||||
<a
|
||||
href=""
|
||||
<button
|
||||
className="footer-icon"
|
||||
onClick={this.props.clickDownloadGraph}
|
||||
title="Save canvas as SVG (does not include search highlighting)"
|
||||
@@ -52,24 +51,25 @@ class DebugMenu extends React.Component {
|
||||
<span className="description">
|
||||
Save canvas as SVG (does not include search highlighting)
|
||||
</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
<div className="troubleshooting-menu-item">
|
||||
<a
|
||||
href=""
|
||||
<button
|
||||
className="footer-icon"
|
||||
title="Reset view state"
|
||||
onClick={this.handleClickReset}
|
||||
>
|
||||
<span className="fa fa-undo" />
|
||||
<span className="description">Reset your local view state and reload the page</span>
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
<div className="troubleshooting-menu-item">
|
||||
<a
|
||||
className="footer-icon" title="Report an issue"
|
||||
className="footer-icon"
|
||||
title="Report an issue"
|
||||
href="https://gitreports.com/issue/weaveworks/scope"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="fa fa-bug" />
|
||||
<span className="description">Report a bug</span>
|
||||
|
||||
@@ -58,8 +58,10 @@ class ViewModeSelector extends React.Component {
|
||||
<div className="view-mode-selector-wrapper">
|
||||
{this.renderItem('fa fa-share-alt', 'Graph', GRAPH_VIEW_MODE, this.props.setGraphView)}
|
||||
{this.renderItem('fa fa-table', 'Table', TABLE_VIEW_MODE, this.props.setTableView)}
|
||||
{this.renderItem('fa fa-bar-chart', 'Resources', RESOURCE_VIEW_MODE,
|
||||
this.props.setResourceView, hasResourceView)}
|
||||
{this.renderItem(
|
||||
'fa fa-bar-chart', 'Resources', RESOURCE_VIEW_MODE,
|
||||
this.props.setResourceView, hasResourceView
|
||||
)}
|
||||
</div>
|
||||
<MetricSelector />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,6 @@ import classnames from 'classnames';
|
||||
|
||||
|
||||
class Warning extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
|
||||
@@ -56,9 +56,13 @@ export default class ZoomControl extends React.Component {
|
||||
|
||||
return (
|
||||
<div className="zoom-control">
|
||||
<a className="zoom-in" onClick={this.handleZoomIn}><span className="fa fa-plus" /></a>
|
||||
<button className="zoom-in" onClick={this.handleZoomIn}>
|
||||
<span className="fa fa-plus" />
|
||||
</button>
|
||||
<Slider value={value} max={1} step={SLIDER_STEP} vertical onChange={this.handleChange} />
|
||||
<a className="zoom-out" onClick={this.handleZoomOut}><span className="fa fa-minus" /></a>
|
||||
<button className="zoom-out" onClick={this.handleZoomOut}>
|
||||
<span className="fa fa-minus" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -88,7 +88,9 @@ class ZoomableCanvas extends React.Component {
|
||||
|
||||
handleZoomControlAction(scale) {
|
||||
// Get the center of the SVG and zoom around it.
|
||||
const { top, bottom, left, right } = this.svg.node().getBoundingClientRect();
|
||||
const {
|
||||
top, bottom, left, right
|
||||
} = this.svg.node().getBoundingClientRect();
|
||||
const centerOfCanvas = {
|
||||
x: (left + right) / 2,
|
||||
y: (top + bottom) / 2,
|
||||
@@ -162,9 +164,10 @@ class ZoomableCanvas extends React.Component {
|
||||
}
|
||||
|
||||
handlePan() {
|
||||
let state = this.state;
|
||||
let { state } = this;
|
||||
// Apply the translation respecting the boundaries.
|
||||
state = this.clampedTranslation({ ...state,
|
||||
state = this.clampedTranslation({
|
||||
...state,
|
||||
translateX: this.state.translateX + d3Event.dx,
|
||||
translateY: this.state.translateY + d3Event.dy,
|
||||
});
|
||||
@@ -185,8 +188,12 @@ class ZoomableCanvas extends React.Component {
|
||||
}
|
||||
|
||||
clampedTranslation(state) {
|
||||
const { width, height, canvasMargins, boundContent, layoutLimits } = this.props;
|
||||
const { contentMinX, contentMaxX, contentMinY, contentMaxY } = layoutLimits.toJS();
|
||||
const {
|
||||
width, height, canvasMargins, boundContent, layoutLimits
|
||||
} = this.props;
|
||||
const {
|
||||
contentMinX, contentMaxX, contentMinY, contentMaxY
|
||||
} = layoutLimits.toJS();
|
||||
|
||||
if (boundContent) {
|
||||
// If the content is required to be bounded in any way, the translation will
|
||||
@@ -239,7 +246,8 @@ class ZoomableCanvas extends React.Component {
|
||||
// translation limits). Adapted from:
|
||||
// https://github.com/d3/d3-zoom/blob/807f02c7a5fe496fbd08cc3417b62905a8ce95fa/src/zoom.js#L251
|
||||
const inversePosition = inverseTransform(this.state, position);
|
||||
state = this.clampedTranslation({ ...state,
|
||||
state = this.clampedTranslation({
|
||||
...state,
|
||||
translateX: position.x - (inversePosition.x * scaleX),
|
||||
translateY: position.y - (inversePosition.y * scaleY),
|
||||
});
|
||||
|
||||
@@ -38,9 +38,15 @@ export const NODE_BASE_SIZE = 100;
|
||||
export const EDGE_WAYPOINTS_CAP = 10;
|
||||
|
||||
export const CANVAS_MARGINS = {
|
||||
[GRAPH_VIEW_MODE]: { top: 160, left: 80, right: 80, bottom: 150 },
|
||||
[TABLE_VIEW_MODE]: { top: 220, left: 40, right: 40, bottom: 30 },
|
||||
[RESOURCE_VIEW_MODE]: { top: 140, left: 210, right: 40, bottom: 150 },
|
||||
[GRAPH_VIEW_MODE]: {
|
||||
top: 160, left: 80, right: 80, bottom: 150
|
||||
},
|
||||
[TABLE_VIEW_MODE]: {
|
||||
top: 220, left: 40, right: 40, bottom: 30
|
||||
},
|
||||
[RESOURCE_VIEW_MODE]: {
|
||||
top: 140, left: 210, right: 40, bottom: 150
|
||||
},
|
||||
};
|
||||
|
||||
// Node details table constants
|
||||
|
||||
@@ -23,7 +23,6 @@ const WINDOW_LENGTH = 60;
|
||||
* This component also keeps a historic max of all samples it sees over time.
|
||||
*/
|
||||
export default ComposedComponent => class extends React.Component {
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
@@ -58,7 +57,7 @@ export default ComposedComponent => class extends React.Component {
|
||||
|
||||
updateBuffer(props) {
|
||||
// merge new samples into buffer
|
||||
let buffer = this.state.buffer;
|
||||
let { buffer } = this.state;
|
||||
const nextSamples = makeOrderedMap(props.samples.map(d => [d.date, d.value]));
|
||||
// need to sort again after merge, some new data may have different times for old values
|
||||
buffer = buffer.merge(nextSamples).sortBy(sortDate);
|
||||
|
||||
@@ -15,12 +15,14 @@ const store = configureStore();
|
||||
|
||||
function renderApp() {
|
||||
const App = require('./components/app').default;
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<DevTools />
|
||||
</Provider>
|
||||
), document.getElementById('app'));
|
||||
ReactDOM.render(
|
||||
(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
<DevTools />
|
||||
</Provider>
|
||||
), document.getElementById('app')
|
||||
);
|
||||
}
|
||||
|
||||
renderApp();
|
||||
|
||||
@@ -11,11 +11,13 @@ const store = configureStore();
|
||||
|
||||
function renderApp() {
|
||||
const App = require('./components/app').default;
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
), document.getElementById('app'));
|
||||
ReactDOM.render(
|
||||
(
|
||||
<Provider store={store}>
|
||||
<App />
|
||||
</Provider>
|
||||
), document.getElementById('app')
|
||||
);
|
||||
}
|
||||
|
||||
renderApp();
|
||||
|
||||
@@ -10,15 +10,13 @@ import { highlightedEdgeIdsSelector } from '../../selectors/graph-view/decorator
|
||||
describe('RootReducer', () => {
|
||||
const ActionTypes = require('../../constants/action-types').default;
|
||||
const reducer = require('../root').default;
|
||||
const initialState = require('../root').initialState;
|
||||
const { initialState } = require('../root');
|
||||
const topologyUtils = require('../../utils/topology-utils');
|
||||
const topologySelectors = require('../../selectors/topology');
|
||||
// TODO maybe extract those to topology-utils tests?
|
||||
const activeTopologyOptionsSelector = topologySelectors.activeTopologyOptionsSelector;
|
||||
const getAdjacentNodes = topologyUtils.getAdjacentNodes;
|
||||
const isNodesDisplayEmpty = topologyUtils.isNodesDisplayEmpty;
|
||||
const isTopologyNodeCountZero = topologyUtils.isTopologyNodeCountZero;
|
||||
const getUrlState = require('../../utils/router-utils').getUrlState;
|
||||
const { activeTopologyOptionsSelector } = topologySelectors;
|
||||
const { getAdjacentNodes, isNodesDisplayEmpty, isTopologyNodeCountZero } = topologyUtils;
|
||||
const { getUrlState } = require('../../utils/router-utils');
|
||||
|
||||
// fixtures
|
||||
|
||||
@@ -427,7 +425,8 @@ describe('RootReducer', () => {
|
||||
RouteAction.state = {
|
||||
topologyId: 'topo1',
|
||||
selectedNodeId: null,
|
||||
topologyOptions: {topo1: {option1: 'on'}}};
|
||||
topologyOptions: {topo1: {option1: 'on'}}
|
||||
};
|
||||
|
||||
let nextState = initialState;
|
||||
nextState = reducer(nextState, RouteAction);
|
||||
@@ -445,7 +444,8 @@ describe('RootReducer', () => {
|
||||
RouteAction.state = {
|
||||
topologyId: 'topo1',
|
||||
selectedNodeId: null,
|
||||
topologyOptions: null};
|
||||
topologyOptions: null
|
||||
};
|
||||
let nextState = initialState;
|
||||
nextState = reducer(nextState, RouteAction);
|
||||
nextState = reducer(nextState, ReceiveTopologiesAction);
|
||||
|
||||
@@ -119,8 +119,10 @@ function processTopologies(state, nextTopologies) {
|
||||
// set `selectType` field for topology and sub_topologies options (recursive).
|
||||
const topologiesWithSelectType = visibleTopologies.map(calcSelectType);
|
||||
// cache URLs by ID
|
||||
state = state.set('topologyUrlsById',
|
||||
setTopologyUrlsById(state.get('topologyUrlsById'), topologiesWithSelectType));
|
||||
state = state.set(
|
||||
'topologyUrlsById',
|
||||
setTopologyUrlsById(state.get('topologyUrlsById'), topologiesWithSelectType)
|
||||
);
|
||||
|
||||
const topologiesWithFullnames = addTopologyFullname(topologiesWithSelectType);
|
||||
const immNextTopologies = fromJS(topologiesWithFullnames).sortBy(topologySorter);
|
||||
@@ -144,7 +146,8 @@ function setDefaultTopologyOptions(state, topologyList) {
|
||||
}
|
||||
|
||||
if (defaultOptions.size) {
|
||||
state = state.setIn(['topologyOptions', topology.get('id')],
|
||||
state = state.setIn(
|
||||
['topologyOptions', topology.get('id')],
|
||||
defaultOptions
|
||||
);
|
||||
}
|
||||
@@ -157,8 +160,10 @@ function closeNodeDetails(state, nodeId) {
|
||||
if (nodeDetails.size > 0) {
|
||||
const popNodeId = nodeId || nodeDetails.keySeq().last();
|
||||
// remove pipe if it belongs to the node being closed
|
||||
state = state.update('controlPipes',
|
||||
controlPipes => controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId));
|
||||
state = state.update(
|
||||
'controlPipes',
|
||||
controlPipes => controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId)
|
||||
);
|
||||
state = state.deleteIn(['nodeDetails', popNodeId]);
|
||||
}
|
||||
if (state.get('nodeDetails').size === 0 || state.get('selectedNodeId') === nodeId) {
|
||||
@@ -298,7 +303,8 @@ export function rootReducer(state = initialState, action) {
|
||||
if (prevDetailsStackSize > 1 || prevSelectedNodeId !== action.nodeId) {
|
||||
// dont set origin if a node was already selected, suppresses animation
|
||||
const origin = prevSelectedNodeId === null ? action.origin : null;
|
||||
state = state.setIn(['nodeDetails', action.nodeId],
|
||||
state = state.setIn(
|
||||
['nodeDetails', action.nodeId],
|
||||
{
|
||||
id: action.nodeId,
|
||||
label: action.label,
|
||||
@@ -318,7 +324,8 @@ export function rootReducer(state = initialState, action) {
|
||||
state = state.deleteIn(['nodeDetails', action.nodeId]);
|
||||
state = state.setIn(['nodeDetails', action.nodeId], details);
|
||||
} else {
|
||||
state = state.setIn(['nodeDetails', action.nodeId],
|
||||
state = state.setIn(
|
||||
['nodeDetails', action.nodeId],
|
||||
{
|
||||
id: action.nodeId,
|
||||
label: action.label,
|
||||
@@ -331,8 +338,10 @@ export function rootReducer(state = initialState, action) {
|
||||
}
|
||||
|
||||
case ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE: {
|
||||
state = state.update('nodeDetails',
|
||||
nodeDetails => nodeDetails.filter((v, k) => k === action.nodeId));
|
||||
state = state.update(
|
||||
'nodeDetails',
|
||||
nodeDetails => nodeDetails.filter((v, k) => k === action.nodeId)
|
||||
);
|
||||
state = state.update('controlPipes', controlPipes => controlPipes.clear());
|
||||
state = state.set('selectedNodeId', action.nodeId);
|
||||
|
||||
@@ -551,7 +560,8 @@ export function rootReducer(state = initialState, action) {
|
||||
|
||||
// disregard if node is not selected anymore
|
||||
if (state.hasIn(['nodeDetails', action.details.id])) {
|
||||
state = state.updateIn(['nodeDetails', action.details.id], obj => ({ ...obj,
|
||||
state = state.updateIn(['nodeDetails', action.details.id], obj => ({
|
||||
...obj,
|
||||
notFound: false,
|
||||
timestamp: action.requestTimestamp,
|
||||
details: action.details,
|
||||
@@ -579,11 +589,13 @@ export function rootReducer(state = initialState, action) {
|
||||
return state;
|
||||
}
|
||||
|
||||
log('RECEIVE_NODES_DELTA',
|
||||
log(
|
||||
'RECEIVE_NODES_DELTA',
|
||||
'remove', size(action.delta.remove),
|
||||
'update', size(action.delta.update),
|
||||
'add', size(action.delta.add),
|
||||
'reset', action.delta.reset);
|
||||
'reset', action.delta.reset
|
||||
);
|
||||
|
||||
if (action.delta.reset) {
|
||||
state = state.set('nodes', makeMap());
|
||||
@@ -625,7 +637,8 @@ export function rootReducer(state = initialState, action) {
|
||||
|
||||
case ActionTypes.RECEIVE_NOT_FOUND: {
|
||||
if (state.hasIn(['nodeDetails', action.nodeId])) {
|
||||
state = state.updateIn(['nodeDetails', action.nodeId], obj => ({ ...obj,
|
||||
state = state.updateIn(['nodeDetails', action.nodeId], obj => ({
|
||||
...obj,
|
||||
timestamp: action.requestTimestamp,
|
||||
notFound: true,
|
||||
}));
|
||||
@@ -700,8 +713,7 @@ export function rootReducer(state = initialState, action) {
|
||||
state = state.update('controlPipes', controlPipes => controlPipes.clear());
|
||||
}
|
||||
if (action.state.nodeDetails) {
|
||||
const actionNodeDetails = makeOrderedMap(
|
||||
action.state.nodeDetails.map(obj => [obj.id, obj]));
|
||||
const actionNodeDetails = makeOrderedMap(action.state.nodeDetails.map(h => [h.id, h]));
|
||||
// check if detail IDs have changed
|
||||
if (!isDeepEqual(state.get('nodeDetails').keySeq(), actionNodeDetails.keySeq())) {
|
||||
state = state.set('nodeDetails', actionNodeDetails);
|
||||
@@ -709,8 +721,10 @@ export function rootReducer(state = initialState, action) {
|
||||
} else {
|
||||
state = state.update('nodeDetails', nodeDetails => nodeDetails.clear());
|
||||
}
|
||||
state = state.set('topologyOptions',
|
||||
fromJS(action.state.topologyOptions) || state.get('topologyOptions'));
|
||||
state = state.set(
|
||||
'topologyOptions',
|
||||
fromJS(action.state.topologyOptions) || state.get('topologyOptions')
|
||||
);
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ export const canvasMarginsSelector = createSelector(
|
||||
[
|
||||
state => state.get('topologyViewMode'),
|
||||
],
|
||||
viewMode => CANVAS_MARGINS[viewMode] || { top: 0, left: 0, right: 0, bottom: 0 }
|
||||
viewMode => CANVAS_MARGINS[viewMode] || {
|
||||
top: 0, left: 0, right: 0, bottom: 0
|
||||
}
|
||||
);
|
||||
|
||||
export const canvasWidthSelector = createSelector(
|
||||
|
||||
@@ -28,7 +28,9 @@ const translationToViewportCenterSelector = createSelector(
|
||||
graphZoomStateSelector,
|
||||
],
|
||||
(centerX, centerY, zoomState) => {
|
||||
const { scaleX, scaleY, translateX, translateY } = zoomState.toJS();
|
||||
const {
|
||||
scaleX, scaleY, translateX, translateY
|
||||
} = zoomState.toJS();
|
||||
return {
|
||||
x: (-translateX + centerX) / scaleX,
|
||||
y: (-translateY + centerY) / scaleY,
|
||||
|
||||
@@ -22,7 +22,9 @@ const graphBoundingRectangleSelector = createSelector(
|
||||
const xMax = graphNodes.map(n => n.get('x') + NODE_BASE_SIZE).max();
|
||||
const yMax = graphNodes.map(n => n.get('y') + NODE_BASE_SIZE).max();
|
||||
|
||||
return makeMap({ xMin, yMin, xMax, yMax });
|
||||
return makeMap({
|
||||
xMin, yMin, xMax, yMax
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -37,7 +39,9 @@ export const graphDefaultZoomSelector = createSelector(
|
||||
(boundingRectangle, canvasMargins, width, height) => {
|
||||
if (!boundingRectangle) return makeMap();
|
||||
|
||||
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
|
||||
const {
|
||||
xMin, xMax, yMin, yMax
|
||||
} = boundingRectangle.toJS();
|
||||
const xFactor = width / (xMax - xMin);
|
||||
const yFactor = height / (yMax - yMin);
|
||||
|
||||
@@ -65,7 +69,9 @@ export const graphLimitsSelector = createSelector(
|
||||
(boundingRectangle) => {
|
||||
if (!boundingRectangle) return makeMap();
|
||||
|
||||
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
|
||||
const {
|
||||
xMin, xMax, yMin, yMax
|
||||
} = boundingRectangle.toJS();
|
||||
|
||||
return makeMap({
|
||||
minScale: MIN_SCALE,
|
||||
|
||||
@@ -85,8 +85,8 @@ const decoratedNodesByTopologySelector = createSelector(
|
||||
const isBaseLayer = (index === 0);
|
||||
|
||||
const nodeParentDecorator = nodeParentDecoratorByTopologyId(parentLayerTopologyId);
|
||||
const nodeMetricSummaryDecorator = nodeMetricSummaryDecoratorByType(
|
||||
pinnedMetricType, showCapacity);
|
||||
const nodeMetricSummaryDecorator =
|
||||
nodeMetricSummaryDecoratorByType(pinnedMetricType, showCapacity);
|
||||
|
||||
// Color the node, deduce its anchor point, dimensions and info about its pinned metric.
|
||||
const decoratedTopologyNodes = (topologyNodes || makeMap())
|
||||
|
||||
@@ -32,7 +32,9 @@ const resourceNodesBoundingRectangleSelector = createSelector(
|
||||
const xMax = flattenedNodes.map(n => n.get('offset') + n.get('width')).max();
|
||||
const yMax = verticalPositions.toList().max() + RESOURCES_LAYER_HEIGHT;
|
||||
|
||||
return makeMap({ xMin, xMax, yMin, yMax });
|
||||
return makeMap({
|
||||
xMin, xMax, yMin, yMax
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@@ -47,7 +49,9 @@ export const resourcesDefaultZoomSelector = createSelector(
|
||||
(boundingRectangle, canvasMargins, width, height) => {
|
||||
if (!boundingRectangle) return makeMap();
|
||||
|
||||
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
|
||||
const {
|
||||
xMin, xMax, yMin, yMax
|
||||
} = boundingRectangle.toJS();
|
||||
|
||||
// The default scale takes all the available horizontal space and 70% of the vertical space.
|
||||
const scaleX = (width / (xMax - xMin)) * 1.0;
|
||||
@@ -76,7 +80,9 @@ export const resourcesLimitsSelector = createSelector(
|
||||
(defaultZoom, boundingRectangle, minNodeWidth, width) => {
|
||||
if (defaultZoom.isEmpty()) return makeMap();
|
||||
|
||||
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
|
||||
const {
|
||||
xMin, xMax, yMin, yMax
|
||||
} = boundingRectangle.toJS();
|
||||
|
||||
return makeMap({
|
||||
// Maximal zoom is such that the smallest box takes the whole canvas.
|
||||
|
||||
@@ -13,14 +13,14 @@ export const activeTopologyZoomCacheKeyPathSelector = createSelector(
|
||||
state => JSON.stringify(activeTopologyOptionsSelector(state)),
|
||||
],
|
||||
(isGraphViewMode, viewMode, topologyId, pinnedMetricType, topologyOptions) => (
|
||||
isGraphViewMode ?
|
||||
isGraphViewMode
|
||||
// In graph view, selecting different options/filters produces a different layout.
|
||||
['zoomCache', viewMode, topologyId, topologyOptions] :
|
||||
? ['zoomCache', viewMode, topologyId, topologyOptions]
|
||||
// Otherwise we're in the resource view where the options are hidden (for now),
|
||||
// but pinning different metrics can result in very different layouts.
|
||||
// TODO: Take `topologyId` into account once the resource
|
||||
// view layouts start differing between the topologies.
|
||||
['zoomCache', viewMode, pinnedMetricType]
|
||||
: ['zoomCache', viewMode, pinnedMetricType]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -11,11 +11,13 @@ const store = configureStore();
|
||||
|
||||
function renderApp() {
|
||||
const TerminalApp = require('./components/terminal-app').default;
|
||||
ReactDOM.render((
|
||||
<Provider store={store}>
|
||||
<TerminalApp />
|
||||
</Provider>
|
||||
), document.getElementById('app'));
|
||||
ReactDOM.render(
|
||||
(
|
||||
<Provider store={store}>
|
||||
<TerminalApp />
|
||||
</Provider>
|
||||
), document.getElementById('app')
|
||||
);
|
||||
}
|
||||
|
||||
renderApp();
|
||||
|
||||
@@ -15,10 +15,18 @@ describe('LayouterUtils', () => {
|
||||
c: {}
|
||||
});
|
||||
expect(initEdgesFromNodes(input).toJS()).toEqual({
|
||||
[edge('a', 'b')]: { id: edge('a', 'b'), source: 'a', target: 'b', value: 1 },
|
||||
[edge('a', 'c')]: { id: edge('a', 'c'), source: 'a', target: 'c', value: 1 },
|
||||
[edge('b', 'a')]: { id: edge('b', 'a'), source: 'b', target: 'a', value: 1 },
|
||||
[edge('b', 'b')]: { id: edge('b', 'b'), source: 'b', target: 'b', value: 1 },
|
||||
[edge('a', 'b')]: {
|
||||
id: edge('a', 'b'), source: 'a', target: 'b', value: 1
|
||||
},
|
||||
[edge('a', 'c')]: {
|
||||
id: edge('a', 'c'), source: 'a', target: 'c', value: 1
|
||||
},
|
||||
[edge('b', 'a')]: {
|
||||
id: edge('b', 'a'), source: 'b', target: 'a', value: 1
|
||||
},
|
||||
[edge('b', 'b')]: {
|
||||
id: edge('b', 'b'), source: 'b', target: 'b', value: 1
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,9 +37,15 @@ describe('MathUtils', () => {
|
||||
expect(f(fromJS({...entryA, ...entryB}))).toBe(30);
|
||||
expect(f(fromJS({...entryA, ...entryC}))).toBe(40);
|
||||
expect(f(fromJS({...entryB, ...entryC}))).toBe(50);
|
||||
expect(f(fromJS({...entryA, ...entryB, ...entryC, ...entryD}))).toBe(30);
|
||||
expect(f(fromJS({...entryA, ...entryB, ...entryC, ...entryD, ...entryE}))).toBe(1);
|
||||
expect(f(fromJS({...entryA, ...entryB, ...entryC, ...entryD, ...entryF}))).toBe(0);
|
||||
expect(f(fromJS({
|
||||
...entryA, ...entryB, ...entryC, ...entryD
|
||||
}))).toBe(30);
|
||||
expect(f(fromJS({
|
||||
...entryA, ...entryB, ...entryC, ...entryD, ...entryE
|
||||
}))).toBe(1);
|
||||
expect(f(fromJS({
|
||||
...entryA, ...entryB, ...entryC, ...entryD, ...entryF
|
||||
}))).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -125,18 +125,24 @@ describe('SearchUtils', () => {
|
||||
|
||||
it('does not add a non-matching field', () => {
|
||||
let matches = fromJS({});
|
||||
matches = fun(matches, ['node1', 'field1'],
|
||||
'some value', 'some query', null, 'some label');
|
||||
matches = fun(
|
||||
matches, ['node1', 'field1'],
|
||||
'some value', 'some query', null, 'some label'
|
||||
);
|
||||
expect(matches.size).toBe(0);
|
||||
});
|
||||
|
||||
it('adds a matching field', () => {
|
||||
let matches = fromJS({});
|
||||
matches = fun(matches, ['node1', 'field1'],
|
||||
'samevalue', 'samevalue', null, 'some label');
|
||||
matches = fun(
|
||||
matches, ['node1', 'field1'],
|
||||
'samevalue', 'samevalue', null, 'some label'
|
||||
);
|
||||
expect(matches.size).toBe(1);
|
||||
expect(matches.getIn(['node1', 'field1'])).toBeDefined();
|
||||
const {text, label, start, length} = matches.getIn(['node1', 'field1']);
|
||||
const {
|
||||
text, label, start, length
|
||||
} = matches.getIn(['node1', 'field1']);
|
||||
expect(text).toBe('samevalue');
|
||||
expect(label).toBe('some label');
|
||||
expect(start).toBe(0);
|
||||
@@ -145,15 +151,19 @@ describe('SearchUtils', () => {
|
||||
|
||||
it('does not add a field when the prefix does not match the label', () => {
|
||||
let matches = fromJS({});
|
||||
matches = fun(matches, ['node1', 'field1'],
|
||||
'samevalue', 'samevalue', 'some prefix', 'some label');
|
||||
matches = fun(
|
||||
matches, ['node1', 'field1'],
|
||||
'samevalue', 'samevalue', 'some prefix', 'some label'
|
||||
);
|
||||
expect(matches.size).toBe(0);
|
||||
});
|
||||
|
||||
it('adds a field when the prefix matches the label', () => {
|
||||
let matches = fromJS({});
|
||||
matches = fun(matches, ['node1', 'field1'],
|
||||
'samevalue', 'samevalue', 'prefix', 'prefixed label');
|
||||
matches = fun(
|
||||
matches, ['node1', 'field1'],
|
||||
'samevalue', 'samevalue', 'prefix', 'prefixed label'
|
||||
);
|
||||
expect(matches.size).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -163,30 +173,40 @@ describe('SearchUtils', () => {
|
||||
|
||||
it('does not add a non-matching field', () => {
|
||||
let matches = fromJS({});
|
||||
matches = fun(matches, ['node1', 'field1'],
|
||||
1, 'metric1', 'metric2', 'lt', 2);
|
||||
matches = fun(
|
||||
matches, ['node1', 'field1'],
|
||||
1, 'metric1', 'metric2', 'lt', 2
|
||||
);
|
||||
expect(matches.size).toBe(0);
|
||||
});
|
||||
|
||||
it('adds a matching field', () => {
|
||||
let matches = fromJS({});
|
||||
matches = fun(matches, ['node1', 'field1'],
|
||||
1, 'metric1', 'metric1', 'lt', 2);
|
||||
matches = fun(
|
||||
matches, ['node1', 'field1'],
|
||||
1, 'metric1', 'metric1', 'lt', 2
|
||||
);
|
||||
expect(matches.size).toBe(1);
|
||||
expect(matches.getIn(['node1', 'field1'])).toBeDefined();
|
||||
const { metric } = matches.getIn(['node1', 'field1']);
|
||||
expect(metric).toBeTruthy();
|
||||
|
||||
matches = fun(matches, ['node2', 'field1'],
|
||||
1, 'metric1', 'metric1', 'gt', 0);
|
||||
matches = fun(
|
||||
matches, ['node2', 'field1'],
|
||||
1, 'metric1', 'metric1', 'gt', 0
|
||||
);
|
||||
expect(matches.size).toBe(2);
|
||||
|
||||
matches = fun(matches, ['node3', 'field1'],
|
||||
1, 'metric1', 'metric1', 'eq', 1);
|
||||
matches = fun(
|
||||
matches, ['node3', 'field1'],
|
||||
1, 'metric1', 'metric1', 'eq', 1
|
||||
);
|
||||
expect(matches.size).toBe(3);
|
||||
|
||||
matches = fun(matches, ['node3', 'field1'],
|
||||
1, 'metric1', 'metric1', 'other', 1);
|
||||
matches = fun(
|
||||
matches, ['node3', 'field1'],
|
||||
1, 'metric1', 'metric1', 'other', 1
|
||||
);
|
||||
expect(matches.size).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,7 +79,8 @@ describe('TopologyUtils', () => {
|
||||
it('sets node degrees', () => {
|
||||
nodes = TopologyUtils.updateNodeDegrees(
|
||||
nodeSets.initial4.nodes,
|
||||
nodeSets.initial4.edges).toJS();
|
||||
nodeSets.initial4.edges
|
||||
).toJS();
|
||||
|
||||
expect(nodes.n1.degree).toEqual(2);
|
||||
expect(nodes.n2.degree).toEqual(1);
|
||||
@@ -88,7 +89,8 @@ describe('TopologyUtils', () => {
|
||||
|
||||
nodes = TopologyUtils.updateNodeDegrees(
|
||||
nodeSets.removeEdge24.nodes,
|
||||
nodeSets.removeEdge24.edges).toJS();
|
||||
nodeSets.removeEdge24.edges
|
||||
).toJS();
|
||||
|
||||
expect(nodes.n1.degree).toEqual(2);
|
||||
expect(nodes.n2.degree).toEqual(0);
|
||||
@@ -97,7 +99,8 @@ describe('TopologyUtils', () => {
|
||||
|
||||
nodes = TopologyUtils.updateNodeDegrees(
|
||||
nodeSets.single3.nodes,
|
||||
nodeSets.single3.edges).toJS();
|
||||
nodeSets.single3.edges
|
||||
).toJS();
|
||||
|
||||
expect(nodes.n1.degree).toEqual(0);
|
||||
expect(nodes.n2.degree).toEqual(0);
|
||||
|
||||
@@ -8,8 +8,7 @@ export function uniformSelect(array, size) {
|
||||
}
|
||||
|
||||
return range(size).map(index =>
|
||||
array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]
|
||||
);
|
||||
array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]);
|
||||
}
|
||||
|
||||
export function insertElement(array, index, element) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { scaleLinear } from 'd3-scale';
|
||||
import { extent } from 'd3-array';
|
||||
|
||||
// Inspired by Lee Byron's test data generator.
|
||||
/*eslint-disable */
|
||||
/* eslint-disable */
|
||||
function bumpLayer(n, maxValue) {
|
||||
function bump(a) {
|
||||
const x = 1 / (0.1 + Math.random());
|
||||
@@ -23,7 +23,7 @@ function bumpLayer(n, maxValue) {
|
||||
const s = scaleLinear().domain(extent(values)).range([0, maxValue]);
|
||||
return values.map(s);
|
||||
}
|
||||
/*eslint-enable */
|
||||
/* eslint-enable */
|
||||
|
||||
|
||||
const nodeData = {};
|
||||
|
||||
@@ -76,7 +76,8 @@ function download(source, name) {
|
||||
filename = `${window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${(+new Date())}`;
|
||||
}
|
||||
|
||||
const url = window.URL.createObjectURL(new Blob(source,
|
||||
const url = window.URL.createObjectURL(new Blob(
|
||||
source,
|
||||
{ type: 'text/xml' }
|
||||
));
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@ export function initEdgesFromNodes(nodes) {
|
||||
// The direction source->target is important since dagre takes
|
||||
// directionality into account when calculating the layout.
|
||||
const edgeId = constructEdgeId(source, target);
|
||||
const edge = makeMap({ id: edgeId, value: 1, source, target });
|
||||
const edge = makeMap({
|
||||
id: edgeId, value: 1, source, target
|
||||
});
|
||||
edges = edges.set(edgeId, edge);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -26,10 +26,10 @@ export function getMetricValue(metric) {
|
||||
return {height: 0, value: null, formattedValue: 'n/a'};
|
||||
}
|
||||
const m = metric.toJS();
|
||||
const value = m.value;
|
||||
const { value } = m;
|
||||
|
||||
let valuePercentage = value === 0 ? 0 : value / m.max;
|
||||
let max = m.max;
|
||||
let { max } = m;
|
||||
if (includes(['load1', 'load5', 'load15'], m.id)) {
|
||||
valuePercentage = loadScale(value);
|
||||
max = null;
|
||||
|
||||
@@ -22,7 +22,9 @@ function curvedUnitPolygonPath(n) {
|
||||
|
||||
export const circleShapeProps = { r: 1 };
|
||||
export const triangleShapeProps = { d: curvedUnitPolygonPath(3) };
|
||||
export const squareShapeProps = { width: 1.8, height: 1.8, rx: 0.4, ry: 0.4, x: -0.9, y: -0.9 };
|
||||
export const squareShapeProps = {
|
||||
width: 1.8, height: 1.8, rx: 0.4, ry: 0.4, x: -0.9, y: -0.9
|
||||
};
|
||||
export const pentagonShapeProps = { d: curvedUnitPolygonPath(5) };
|
||||
export const hexagonShapeProps = { d: curvedUnitPolygonPath(6) };
|
||||
export const heptagonShapeProps = { d: curvedUnitPolygonPath(7) };
|
||||
|
||||
@@ -72,8 +72,12 @@ function findNodeMatch(nodeMatches, keyPath, text, query, prefix, label, truncat
|
||||
if (matches) {
|
||||
const firstMatch = matches[0];
|
||||
const index = text.search(queryRe);
|
||||
nodeMatches = nodeMatches.setIn(keyPath,
|
||||
{text, label, start: index, length: firstMatch.length, truncate});
|
||||
nodeMatches = nodeMatches.setIn(
|
||||
keyPath,
|
||||
{
|
||||
text, label, start: index, length: firstMatch.length, truncate
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return nodeMatches;
|
||||
@@ -110,14 +114,18 @@ function findNodeMatchMetric(nodeMatches, keyPath, fieldValue, fieldLabel, metri
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
nodeMatches = nodeMatches.setIn(keyPath,
|
||||
{fieldLabel, metric: true});
|
||||
nodeMatches = nodeMatches.setIn(
|
||||
keyPath,
|
||||
{fieldLabel, metric: true}
|
||||
);
|
||||
}
|
||||
}
|
||||
return nodeMatches;
|
||||
}
|
||||
|
||||
export function searchNode(node, { prefix, query, metric, comp, value }) {
|
||||
export function searchNode(node, {
|
||||
prefix, query, metric, comp, value
|
||||
}) {
|
||||
let nodeMatches = makeMap();
|
||||
|
||||
if (query) {
|
||||
@@ -125,8 +133,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
|
||||
SEARCH_FIELDS.forEach((field, label) => {
|
||||
const keyPath = [label];
|
||||
if (node.has(field)) {
|
||||
nodeMatches = findNodeMatch(nodeMatches, keyPath, node.get(field),
|
||||
query, prefix, label);
|
||||
nodeMatches = findNodeMatch(
|
||||
nodeMatches, keyPath, node.get(field),
|
||||
query, prefix, label
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -134,8 +144,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
|
||||
if (node.get('metadata')) {
|
||||
node.get('metadata').forEach((field) => {
|
||||
const keyPath = ['metadata', field.get('id')];
|
||||
nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
|
||||
query, prefix, field.get('label'), field.get('truncate'));
|
||||
nodeMatches = findNodeMatch(
|
||||
nodeMatches, keyPath, field.get('value'),
|
||||
query, prefix, field.get('label'), field.get('truncate')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -143,8 +155,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
|
||||
if (node.get('parents')) {
|
||||
node.get('parents').forEach((parent) => {
|
||||
const keyPath = ['parents', parent.get('id')];
|
||||
nodeMatches = findNodeMatch(nodeMatches, keyPath, parent.get('label'),
|
||||
query, prefix, parent.get('topologyId'));
|
||||
nodeMatches = findNodeMatch(
|
||||
nodeMatches, keyPath, parent.get('label'),
|
||||
query, prefix, parent.get('topologyId')
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -153,8 +167,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
|
||||
(propertyList.get('rows') || []).forEach((row) => {
|
||||
const entries = row.get('entries');
|
||||
const keyPath = ['property-lists', row.get('id')];
|
||||
nodeMatches = findNodeMatch(nodeMatches, keyPath, entries.get('value'),
|
||||
query, prefix, entries.get('label'));
|
||||
nodeMatches = findNodeMatch(
|
||||
nodeMatches, keyPath, entries.get('value'),
|
||||
query, prefix, entries.get('label')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,8 +189,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
|
||||
if (metrics) {
|
||||
metrics.forEach((field) => {
|
||||
const keyPath = ['metrics', field.get('id')];
|
||||
nodeMatches = findNodeMatchMetric(nodeMatches, keyPath, field.get('value'),
|
||||
field.get('label'), metric, comp, value);
|
||||
nodeMatches = findNodeMatchMetric(
|
||||
nodeMatches, keyPath, field.get('value'),
|
||||
field.get('label'), metric, comp, value
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -221,7 +239,7 @@ export function parseQuery(query) {
|
||||
if (comparisonQuery && comparisonQuery.length === 2) {
|
||||
const value = parseValue(comparisonQuery[1]);
|
||||
const metric = comparisonQuery[0].trim();
|
||||
if (!isNaN(value) && metric) {
|
||||
if (!window.isNaN(value) && metric) {
|
||||
comparison = {
|
||||
metric,
|
||||
value,
|
||||
@@ -259,8 +277,7 @@ export function getSearchableFields(nodes) {
|
||||
// Consider only property lists (and not generic tables).
|
||||
const tableRowLabels = nodes.reduce((labels, node) => (
|
||||
labels.union(get(node, 'tables').filter(isPropertyList).flatMap(t => (t.get('rows') || makeList)
|
||||
.map(f => f.getIn(['entries', 'label']))
|
||||
))
|
||||
.map(f => f.getIn(['entries', 'label']))))
|
||||
), makeSet());
|
||||
|
||||
const metricLabels = nodes.reduce((labels, node) => (
|
||||
@@ -282,8 +299,10 @@ export function getSearchableFields(nodes) {
|
||||
*/
|
||||
export function applyPinnedSearches(state) {
|
||||
// clear old filter state
|
||||
state = state.update('nodes',
|
||||
nodes => nodes.map(node => node.set('filtered', false)));
|
||||
state = state.update(
|
||||
'nodes',
|
||||
nodes => nodes.map(node => node.set('filtered', false))
|
||||
);
|
||||
|
||||
const pinnedSearches = state.get('pinnedSearches');
|
||||
if (pinnedSearches.size > 0) {
|
||||
@@ -292,10 +311,12 @@ export function applyPinnedSearches(state) {
|
||||
if (parsed) {
|
||||
const nodeMatches = searchTopology(state.get('nodes'), parsed);
|
||||
const filteredNodes = state.get('nodes')
|
||||
.map(node => node.set('filtered',
|
||||
.map(node => node.set(
|
||||
'filtered',
|
||||
node.get('filtered') // matched by previous pinned search
|
||||
|| nodeMatches.size === 0 // no match, filter all nodes
|
||||
|| !nodeMatches.has(node.get('id')))); // filter matches
|
||||
|| !nodeMatches.has(node.get('id'))
|
||||
)); // filter matches
|
||||
state = state.set('nodes', filteredNodes);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,7 +4,9 @@ const applyTranslateY = ({ scaleY = 1, translateY = 0 }, y) => (y * scaleY) + tr
|
||||
const applyScaleX = ({ scaleX = 1 }, width) => width * scaleX;
|
||||
const applyScaleY = ({ scaleY = 1 }, height) => height * scaleY;
|
||||
|
||||
export const applyTransform = (transform, { width = 0, height = 0, x, y }) => ({
|
||||
export const applyTransform = (transform, {
|
||||
width = 0, height = 0, x, y
|
||||
}) => ({
|
||||
x: applyTranslateX(transform, x),
|
||||
y: applyTranslateY(transform, y),
|
||||
width: applyScaleX(transform, width),
|
||||
@@ -17,7 +19,9 @@ const inverseTranslateY = ({ scaleY = 1, translateY = 0 }, y) => (y - translateY
|
||||
const inverseScaleX = ({ scaleX = 1 }, width) => width / scaleX;
|
||||
const inverseScaleY = ({ scaleY = 1 }, height) => height / scaleY;
|
||||
|
||||
export const inverseTransform = (transform, { width = 0, height = 0, x, y }) => ({
|
||||
export const inverseTransform = (transform, {
|
||||
width = 0, height = 0, x, y
|
||||
}) => ({
|
||||
x: inverseTranslateX(transform, x),
|
||||
y: inverseTranslateY(transform, y),
|
||||
width: inverseScaleX(transform, width),
|
||||
@@ -25,6 +29,8 @@ export const inverseTransform = (transform, { width = 0, height = 0, x, y }) =>
|
||||
});
|
||||
|
||||
|
||||
export const transformToString = ({ translateX = 0, translateY = 0, scaleX = 1, scaleY = 1 }) => (
|
||||
export const transformToString = ({
|
||||
translateX = 0, translateY = 0, scaleX = 1, scaleY = 1
|
||||
}) => (
|
||||
`translate(${translateX},${translateY}) scale(${scaleX},${scaleY})`
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ const csrfToken = (() => {
|
||||
// Check for token at window level or parent level (for iframe);
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
const token = typeof window !== 'undefined'
|
||||
? window.__WEAVEWORKS_CSRF_TOKEN || parent.__WEAVEWORKS_CSRF_TOKEN
|
||||
? window.__WEAVEWORKS_CSRF_TOKEN || window.parent.__WEAVEWORKS_CSRF_TOKEN
|
||||
: null;
|
||||
/* eslint-enable no-underscore-dangle */
|
||||
if (!token || token === '$__CSRF_TOKEN_PLACEHOLDER__') {
|
||||
@@ -110,7 +110,7 @@ function topologiesUrl(state) {
|
||||
}
|
||||
|
||||
export function getWebsocketUrl(host = window.location.host, pathname = window.location.pathname) {
|
||||
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
const wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
return `${wsProto}://${host}${process.env.SCOPE_API_PREFIX || ''}${basePath(pathname)}`;
|
||||
}
|
||||
|
||||
@@ -167,8 +167,10 @@ function createWebsocket(websocketUrl, getState, dispatch) {
|
||||
firstMessageOnWebsocketAt = new Date();
|
||||
const timeToFirstMessage = firstMessageOnWebsocketAt - createWebsocketAt;
|
||||
if (timeToFirstMessage > FIRST_RENDER_TOO_LONG_THRESHOLD) {
|
||||
log('Time (ms) to first nodes render after websocket was created',
|
||||
firstMessageOnWebsocketAt - createWebsocketAt);
|
||||
log(
|
||||
'Time (ms) to first nodes render after websocket was created',
|
||||
firstMessageOnWebsocketAt - createWebsocketAt
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -197,12 +199,15 @@ function getNodesForTopologies(state, dispatch, topologyIds, topologyOptions = m
|
||||
// fetch sequentially
|
||||
state.get('topologyUrlsById')
|
||||
.filter((_, topologyId) => topologyIds.contains(topologyId))
|
||||
.reduce((sequence, topologyUrl, topologyId) => sequence.then(() => {
|
||||
const optionsQuery = buildUrlQuery(topologyOptions.get(topologyId), state);
|
||||
return doRequest({ url: `${getApiPath()}${topologyUrl}?${optionsQuery}` });
|
||||
})
|
||||
.then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
|
||||
Promise.resolve());
|
||||
.reduce(
|
||||
(sequence, topologyUrl, topologyId) => sequence
|
||||
.then(() => {
|
||||
const optionsQuery = buildUrlQuery(topologyOptions.get(topologyId), state);
|
||||
return doRequest({ url: `${getApiPath()}${topologyUrl}?${optionsQuery}` });
|
||||
})
|
||||
.then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
|
||||
Promise.resolve()
|
||||
);
|
||||
}
|
||||
|
||||
function getNodesOnce(getState, dispatch) {
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
url("../../node_modules/materialize-css/fonts/roboto/Roboto-Regular.ttf");
|
||||
}
|
||||
|
||||
// TODO: Remove this line once Service UI CONFIGURE button stops being added to Scope.
|
||||
.scope-wrapper .setup-nav-button { display: none; }
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -109,16 +106,19 @@ a {
|
||||
position: absolute;
|
||||
bottom: 11px;
|
||||
|
||||
a {
|
||||
button {
|
||||
@extend .btn-opacity;
|
||||
background-color: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
color: $text-secondary-color;
|
||||
cursor: pointer;
|
||||
line-height: 20px;
|
||||
padding: 1px 3px;
|
||||
outline: 0;
|
||||
|
||||
.fa {
|
||||
font-size: 150%;
|
||||
font-size: 125%;
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
@@ -1016,8 +1016,12 @@ a {
|
||||
color: $text-tertiary-color;
|
||||
font-size: 90%;
|
||||
text-align: right;
|
||||
cursor: pointer;
|
||||
padding: 0 4px;
|
||||
padding: 0;
|
||||
|
||||
.node-details-table-header-sortable {
|
||||
padding: 3px 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-sorted {
|
||||
color: $text-secondary-color;
|
||||
@@ -1135,6 +1139,14 @@ a {
|
||||
}
|
||||
}
|
||||
|
||||
.node-details-table-node-link {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.node-details-table-node-link,
|
||||
.node-details-table-node-label,
|
||||
.node-details-table-node-value {
|
||||
@@ -2118,6 +2130,22 @@ a {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
button {
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
button, a {
|
||||
color: $weave-charcoal-blue;
|
||||
|
||||
&:hover {
|
||||
color: $text-color;
|
||||
}
|
||||
}
|
||||
|
||||
.fa {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
"autoprefixer": "6.7.7",
|
||||
"babel-cli": "6.24.1",
|
||||
"babel-core": "6.24.1",
|
||||
"babel-eslint": "7.2.3",
|
||||
"babel-eslint": "8.0.1",
|
||||
"babel-jest": "19.0.0",
|
||||
"babel-loader": "7.0.0",
|
||||
"babel-plugin-transform-object-rest-spread": "6.23.0",
|
||||
@@ -60,12 +60,12 @@
|
||||
"babel-preset-react": "6.24.1",
|
||||
"clean-webpack-plugin": "0.1.16",
|
||||
"css-loader": "0.28.1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-config-airbnb": "14.1.0",
|
||||
"eslint-loader": "1.7.1",
|
||||
"eslint-plugin-import": "2.2.0",
|
||||
"eslint-plugin-jsx-a11y": "4.0.0",
|
||||
"eslint-plugin-react": "6.10.3",
|
||||
"eslint": "4.9.0",
|
||||
"eslint-config-airbnb": "16.1.0",
|
||||
"eslint-loader": "1.9.0",
|
||||
"eslint-plugin-import": "2.7.0",
|
||||
"eslint-plugin-jsx-a11y": "6.0.2",
|
||||
"eslint-plugin-react": "7.4.0",
|
||||
"expect": "1.20.2",
|
||||
"extract-text-webpack-plugin": "2.1.0",
|
||||
"file-loader": "0.11.1",
|
||||
|
||||
@@ -8,12 +8,12 @@ const app = express();
|
||||
|
||||
const BACKEND_HOST = process.env.BACKEND_HOST || 'localhost';
|
||||
|
||||
/**
|
||||
*
|
||||
* Proxy requests to:
|
||||
* - /api -> :4040/api
|
||||
*
|
||||
************************************************************/
|
||||
/*
|
||||
*
|
||||
* Proxy requests to:
|
||||
* - /api -> :4040/api
|
||||
*
|
||||
*/
|
||||
|
||||
const backendProxy = httpProxy.createProxy({
|
||||
ws: true,
|
||||
@@ -22,24 +22,24 @@ const backendProxy = httpProxy.createProxy({
|
||||
backendProxy.on('error', err => console.error('Proxy error', err));
|
||||
app.all('/api*', backendProxy.web.bind(backendProxy));
|
||||
|
||||
/**
|
||||
*
|
||||
* Production env serves precompiled content from build/
|
||||
*
|
||||
************************************************************/
|
||||
/*
|
||||
*
|
||||
* Production env serves precompiled content from build/
|
||||
*
|
||||
*/
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
app.use(express.static('build'));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Webpack Dev Middleware with Hot Reload
|
||||
*
|
||||
* See: https://github.com/webpack/webpack-dev-middleware;
|
||||
* https://github.com/glenjamin/webpack-hot-middleware
|
||||
*
|
||||
*************************************************************/
|
||||
/*
|
||||
*
|
||||
* Webpack Dev Middleware with Hot Reload
|
||||
*
|
||||
* See: https://github.com/webpack/webpack-dev-middleware;
|
||||
* https://github.com/glenjamin/webpack-hot-middleware
|
||||
*
|
||||
*/
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const webpack = require('webpack');
|
||||
@@ -60,11 +60,11 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Express server
|
||||
*
|
||||
*****************/
|
||||
/*
|
||||
*
|
||||
* Express server
|
||||
*
|
||||
*/
|
||||
|
||||
const port = process.env.PORT || 4042;
|
||||
const server = app.listen(port, 'localhost', () => {
|
||||
@@ -76,11 +76,11 @@ const server = app.listen(port, 'localhost', () => {
|
||||
server.on('upgrade', backendProxy.ws.bind(backendProxy));
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* Path proxy server
|
||||
*
|
||||
*************************************************************/
|
||||
/*
|
||||
*
|
||||
* Path proxy server
|
||||
*
|
||||
*/
|
||||
|
||||
const proxyRules = new HttpProxyRules({
|
||||
rules: {
|
||||
@@ -100,8 +100,10 @@ const proxyPathServer = http.createServer((req, res) => {
|
||||
return pathProxy.web(req, res, {target});
|
||||
}).listen(pathProxyPort, 'localhost', () => {
|
||||
const pathProxyHost = proxyPathServer.address().address;
|
||||
console.log('Scope Proxy Path UI listening at http://%s:%s/scoped/',
|
||||
pathProxyHost, pathProxyPort);
|
||||
console.log(
|
||||
'Scope Proxy Path UI listening at http://%s:%s/scoped/',
|
||||
pathProxyHost, pathProxyPort
|
||||
);
|
||||
});
|
||||
|
||||
proxyPathServer.on('upgrade', (req, socket, head) => {
|
||||
|
||||
833
client/yarn.lock
833
client/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user