123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- import debug from 'debug';
- import React from 'react';
- import PropTypes from 'prop-types';
- import classNames from 'classnames';
- import { connect } from 'react-redux';
- import { debounce, isEqual } from 'lodash';
- import { ThemeProvider } from 'styled-components';
- import commonTheme from 'weaveworks-ui-components/lib/theme';
- import GlobalStyle from './global-style';
- // import Logo from './logo';
- import Footer from './footer';
- import Sidebar from './sidebar';
- import HelpPanel from './help-panel';
- import TroubleshootingMenu from './troubleshooting-menu';
- import Search from './search';
- import Status from './status';
- import Topologies from './topologies';
- import TopologyOptions from './topology-options';
- import Overlay from './overlay';
- import {
- pinNextMetric,
- pinPreviousMetric,
- hitEsc,
- unpinMetric,
- toggleHelp,
- setGraphView,
- setMonitorState,
- setTableView,
- setStoreViewState,
- setViewportDimensions,
- } from '../actions/app-actions';
- import {
- focusSearch,
- getApiDetails,
- setResourceView,
- getTopologiesWithInitialPoll,
- shutdown,
- } from '../actions/request-actions';
- import Details from './details';
- import Nodes from './nodes';
- import TimeControl from './time-control';
- import TimeTravelWrapper from './time-travel-wrapper';
- import ViewModeSelector from './view-mode-selector';
- import NetworkSelector from './networks-selector';
- import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar';
- import { getUrlState } from '../utils/router-utils';
- import { getRouter } from '../router';
- import { trackAnalyticsEvent } from '../utils/tracking-utils';
- import { availableNetworksSelector } from '../selectors/node-networks';
- import { timeTravelSupportedSelector } from '../selectors/time-travel';
- import {
- isResourceViewModeSelector,
- isTableViewModeSelector,
- isGraphViewModeSelector,
- } from '../selectors/topology';
- import defaultTheme from '../themes/default';
- import contrastTheme from '../themes/contrast';
- import { VIEWPORT_RESIZE_DEBOUNCE_INTERVAL } from '../constants/timer';
- import {
- ESC_KEY_CODE,
- } from '../constants/key-codes';
- const keyPressLog = debug('scope:app-key-press');
- class App extends React.Component {
- constructor(props, context) {
- super(props, context);
- this.props.dispatch(setMonitorState(this.props.monitor));
- this.props.dispatch(setStoreViewState(!this.props.disableStoreViewState));
- this.setViewportDimensions = this.setViewportDimensions.bind(this);
- this.handleResize = debounce(this.setViewportDimensions, VIEWPORT_RESIZE_DEBOUNCE_INTERVAL);
- this.handleRouteChange = debounce(props.onRouteChange, 50);
- this.saveAppRef = this.saveAppRef.bind(this);
- this.onKeyPress = this.onKeyPress.bind(this);
- this.onKeyUp = this.onKeyUp.bind(this);
- }
- componentDidMount() {
- this.setViewportDimensions();
- window.addEventListener('resize', this.handleResize);
- window.addEventListener('keypress', this.onKeyPress);
- window.addEventListener('keyup', this.onKeyUp);
- this.router = this.props.dispatch(getRouter(this.props.urlState));
- this.router.start({ hashbang: true });
- if (!this.props.routeSet || process.env.WEAVE_CLOUD) {
- // dont request topologies when already done via router.
- // If running as a component, always request topologies when the app mounts.
- this.props.dispatch(getTopologiesWithInitialPoll());
- }
- getApiDetails(this.props.dispatch);
- }
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize);
- window.removeEventListener('keypress', this.onKeyPress);
- window.removeEventListener('keyup', this.onKeyUp);
- this.props.dispatch(shutdown());
- this.router.stop();
- }
- componentWillReceiveProps(nextProps) {
- if (nextProps.monitor !== this.props.monitor) {
- this.props.dispatch(setMonitorState(nextProps.monitor));
- }
- if (nextProps.disableStoreViewState !== this.props.disableStoreViewState) {
- this.props.dispatch(setStoreViewState(!nextProps.disableStoreViewState));
- }
- // Debounce-notify about the route change if the URL state changes its content.
- if (!isEqual(nextProps.urlState, this.props.urlState)) {
- this.handleRouteChange(nextProps.urlState);
- }
- }
- onKeyUp(ev) {
- const { showingTerminal } = this.props;
- keyPressLog('onKeyUp', 'keyCode', ev.keyCode, ev);
- // don't get esc in onKeyPress
- if (ev.keyCode === ESC_KEY_CODE) {
- this.props.dispatch(hitEsc());
- } else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) {
- toggleDebugToolbar();
- this.forceUpdate();
- }
- }
- onKeyPress(ev) {
- const { dispatch, searchFocused, showingTerminal } = this.props;
- //
- // keyup gives 'key'
- // keypress gives 'char'
- // Distinction is important for international keyboard layouts where there
- // is often a different {key: char} mapping.
- if (!searchFocused && !showingTerminal) {
- keyPressLog('onKeyPress', 'keyCode', ev.keyCode, ev);
- const char = String.fromCharCode(ev.charCode);
- if (char === '<') {
- dispatch(pinPreviousMetric());
- this.trackEvent('scope.metric.selector.pin.previous.keypress', {
- metricType: this.props.pinnedMetricType
- });
- } else if (char === '>') {
- dispatch(pinNextMetric());
- this.trackEvent('scope.metric.selector.pin.next.keypress', {
- metricType: this.props.pinnedMetricType
- });
- } else if (char === 'g') {
- dispatch(setGraphView());
- this.trackEvent('scope.layout.selector.keypress');
- } else if (char === 't') {
- dispatch(setTableView());
- this.trackEvent('scope.layout.selector.keypress');
- } else if (char === 'r') {
- dispatch(setResourceView());
- this.trackEvent('scope.layout.selector.keypress');
- } else if (char === 'q') {
- this.trackEvent('scope.metric.selector.unpin.keypress', {
- metricType: this.props.pinnedMetricType
- });
- dispatch(unpinMetric());
- } else if (char === '/') {
- ev.preventDefault();
- ev.stopPropagation();
- dispatch(focusSearch());
- } else if (char === '?') {
- dispatch(toggleHelp());
- }
- }
- }
- trackEvent(eventName, additionalProps = {}) {
- trackAnalyticsEvent(eventName, {
- layout: this.props.topologyViewMode,
- parentTopologyId: this.props.currentTopology.get('parentId'),
- topologyId: this.props.currentTopology.get('id'),
- ...additionalProps,
- });
- }
- setViewportDimensions() {
- if (this.appRef) {
- const { width, height } = this.appRef.getBoundingClientRect();
- this.props.dispatch(setViewportDimensions(width, height));
- }
- }
- saveAppRef(ref) {
- this.appRef = ref;
- }
- render() {
- const {
- isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails,
- showingHelp, showingNetworkSelector, showingTroubleshootingMenu,
- timeTravelTransitioning, timeTravelSupported, contrastMode,
- } = this.props;
- const className = classNames('scope-app', {
- 'contrast-mode': contrastMode,
- 'time-travel-open': timeTravelSupported,
- });
- const isIframe = window !== window.top;
- return (
- <ThemeProvider theme={{...commonTheme, scope: contrastMode ? contrastTheme : defaultTheme }}>
- <>
- <GlobalStyle />
- <div className={className} ref={this.saveAppRef}>
- {showingDebugToolbar() && <DebugToolbar />}
- {showingHelp && <HelpPanel />}
- {showingTroubleshootingMenu && <TroubleshootingMenu />}
- {showingDetails && (
- <Details
- renderNodeDetailsExtras={this.props.renderNodeDetailsExtras}
- />
- )}
- <div className="header">
- {timeTravelSupported && this.props.renderTimeTravel()}
- <div className="selectors">
- {/* <div className="logo">
- {!isIframe
- && (
- <svg width="100%" height="100%" viewBox="0 0 1089 217">
- <Logo />
- </svg>
- )
- }
- </div> */}
- <Search />
- <Topologies />
- <ViewModeSelector />
- <TimeControl />
- </div>
- </div>
- <Nodes />
- <Sidebar classNames={isTableViewMode ? 'sidebar-gridmode' : ''}>
- {/* {showingNetworkSelector && isGraphViewMode && <NetworkSelector />} */}
- {/* {!isResourceViewMode && <Status />} */}
- {!isResourceViewMode && <TopologyOptions />}
- </Sidebar>
- <Footer />
- <Overlay faded={timeTravelTransitioning} />
- </div>
- </>
- </ThemeProvider>
- );
- }
- }
- function mapStateToProps(state) {
- return {
- contrastMode: state.get('contrastMode'),
- currentTopology: state.get('currentTopology'),
- isGraphViewMode: isGraphViewModeSelector(state),
- isResourceViewMode: isResourceViewModeSelector(state),
- isTableViewMode: isTableViewModeSelector(state),
- pinnedMetricType: state.get('pinnedMetricType'),
- routeSet: state.get('routeSet'),
- searchFocused: state.get('searchFocused'),
- searchQuery: state.get('searchQuery'),
- showingDetails: state.get('nodeDetails').size > 0,
- showingHelp: state.get('showingHelp'),
- showingNetworkSelector: availableNetworksSelector(state).count() > 0,
- showingTerminal: state.get('controlPipes').size > 0,
- showingTroubleshootingMenu: state.get('showingTroubleshootingMenu'),
- timeTravelSupported: timeTravelSupportedSelector(state),
- timeTravelTransitioning: state.get('timeTravelTransitioning'),
- topologyViewMode: state.get('topologyViewMode'),
- urlState: getUrlState(state)
- };
- }
- App.propTypes = {
- disableStoreViewState: PropTypes.bool,
- monitor: PropTypes.bool,
- onRouteChange: PropTypes.func,
- renderNodeDetailsExtras: PropTypes.func,
- renderTimeTravel: PropTypes.func,
- };
- App.defaultProps = {
- disableStoreViewState: false,
- monitor: false,
- onRouteChange: () => null,
- renderNodeDetailsExtras: () => null,
- renderTimeTravel: () => <TimeTravelWrapper />,
- };
- export default connect(mapStateToProps)(App);
|