import debug from 'debug'; import React from 'react'; import classNames from 'classnames'; import { connect } from 'react-redux'; import { Map as makeMap } from 'immutable'; import { clickCloseDetails, clickShowTopologyForNode } from '../actions/app-actions'; import { brightenColor, getNeutralColor, getNodeColorDark } from '../utils/color-utils'; import { isGenericTable, isPropertyList } from '../utils/node-details-utils'; import { resetDocumentTitle, setDocumentTitle } from '../utils/title-utils'; import { timestampsEqual } from '../utils/time-utils'; import Overlay from './overlay'; import MatchedText from './matched-text'; import NodeDetailsControls from './node-details/node-details-controls'; import NodeDetailsGenericTable from './node-details/node-details-generic-table'; import NodeDetailsPropertyList from './node-details/node-details-property-list'; import NodeDetailsHealth from './node-details/node-details-health'; import NodeDetailsInfo from './node-details/node-details-info'; import NodeDetailsRelatives from './node-details/node-details-relatives'; import NodeDetailsTable from './node-details/node-details-table'; import Warning from './warning'; import CloudFeature from './cloud-feature'; import NodeDetailsImageStatus from './node-details/node-details-image-status'; const log = debug('scope:node-details'); function getTruncationText(count) { return 'This section was too long to be handled efficiently and has been truncated' + ` (${count} extra entries not included). We are working to remove this limitation.`; } class NodeDetails extends React.Component { constructor(props, context) { super(props, context); this.handleClickClose = this.handleClickClose.bind(this); this.handleShowTopologyForNode = this.handleShowTopologyForNode.bind(this); } handleClickClose(ev) { ev.preventDefault(); this.props.clickCloseDetails(this.props.nodeId); } handleShowTopologyForNode(ev) { ev.preventDefault(); this.props.clickShowTopologyForNode(this.props.topologyId, this.props.nodeId); } componentDidMount() { this.updateTitle(); } componentWillUnmount() { resetDocumentTitle(); } renderTools() { const showSwitchTopology = this.props.nodeId !== this.props.selectedNodeId; const topologyTitle = `View ${this.props.label} in ${this.props.topologyId}`; return (
{showSwitchTopology && Show in {this.props.topologyId.replace(/-/g, ' ')} }
); } renderLoading() { const node = this.props.nodes.get(this.props.nodeId); const label = node ? node.get('label') : this.props.label; // NOTE: If we start the fa-spin animation before the node details panel has been // mounted, the spinner is displayed blurred the whole time in Chrome (possibly // 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()); const tools = this.renderTools(); const styles = { header: { backgroundColor: nodeColor } }; return (
{tools}

{label}

Loading...
); } renderNotAvailable() { const tools = this.renderTools(); return (
{tools}

{this.props.label}

n/a

{this.props.label} is not visible to Scope when it is not communicating. Details will become available here when it communicates again.

); } render() { if (this.props.notFound) { return this.renderNotAvailable(); } if (this.props.details) { return this.renderDetails(); } return this.renderLoading(); } renderDetails() { 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() : {}; const tools = this.renderTools(); const styles = { controls: { backgroundColor: brightenColor(nodeColor) }, header: { backgroundColor: nodeColor } }; return (
{tools}

{details.parents && }
{showControls &&
}
{details.metrics &&
Status
} {details.metadata &&
Info
} {details.connections && details.connections.filter(cs => cs.connections.length > 0) .map(connections => (
))} {details.children && details.children.map(children => (
))} {details.tables && details.tables.length > 0 && details.tables.map((table) => { if (table.rows.length > 0) { return (
{table.label && table.label.length > 0 && table.label} {table.truncationCount > 0 && }
{this.renderTable(table)}
); } return null; })}
); } renderTable(table) { const { nodeMatches = makeMap() } = this.props; if (isGenericTable(table)) { return ( ); } else if (isPropertyList(table)) { return ( ); } log(`Undefined type '${table.type}' for table ${table.id}`); return null; } componentDidUpdate() { this.updateTitle(); } updateTitle() { setDocumentTitle(this.props.details && this.props.details.label); } } function mapStateToProps(state, ownProps) { const currentTopologyId = state.get('currentTopologyId'); return { transitioning: !timestampsEqual(state.get('pausedAt'), ownProps.timestamp), nodeMatches: state.getIn(['searchNodeMatches', currentTopologyId, ownProps.id]), nodes: state.get('nodes'), selectedNodeId: state.get('selectedNodeId'), }; } export default connect( mapStateToProps, { clickCloseDetails, clickShowTopologyForNode } )(NodeDetails);