Fix revert failure chain and thus: add autoRefresh with pause

pull/965/head
Jared Scheib 2017-03-03 15:30:33 -08:00
parent 8a4ccc82d1
commit 2751a00813
26 changed files with 250 additions and 125 deletions

View File

@ -21,6 +21,7 @@
4. [#892](https://github.com/influxdata/chronograf/issues/891): Make dashboard visualizations resizable 4. [#892](https://github.com/influxdata/chronograf/issues/891): Make dashboard visualizations resizable
5. [#893](https://github.com/influxdata/chronograf/issues/893): Persist dashboard visualization position 5. [#893](https://github.com/influxdata/chronograf/issues/893): Persist dashboard visualization position
6. [#922](https://github.com/influxdata/chronograf/issues/922): Additional OAuth2 support for [Heroku](https://github.com/influxdata/chronograf/blob/master/docs/auth.md#heroku) and [Google](https://github.com/influxdata/chronograf/blob/master/docs/auth.md#google) 6. [#922](https://github.com/influxdata/chronograf/issues/922): Additional OAuth2 support for [Heroku](https://github.com/influxdata/chronograf/blob/master/docs/auth.md#heroku) and [Google](https://github.com/influxdata/chronograf/blob/master/docs/auth.md#google)
7. [#781](https://github.com/influxdata/chronograf/issues/781): Add global auto-refresh dropdown to all graph dashboards
### UI Improvements ### UI Improvements
1. [#905](https://github.com/influxdata/chronograf/pull/905): Make scroll bar thumb element bigger 1. [#905](https://github.com/influxdata/chronograf/pull/905): Make scroll bar thumb element bigger

View File

@ -10,6 +10,7 @@ const Dashboard = ({
inPresentationMode, inPresentationMode,
onPositionChange, onPositionChange,
source, source,
autoRefresh,
timeRange, timeRange,
}) => { }) => {
if (dashboard.id === 0) { if (dashboard.id === 0) {
@ -20,14 +21,13 @@ const Dashboard = ({
<div className={classnames({'page-contents': true, 'presentation-mode': inPresentationMode})}> <div className={classnames({'page-contents': true, 'presentation-mode': inPresentationMode})}>
<div className={classnames('container-fluid full-width dashboard', {'dashboard-edit': isEditMode})}> <div className={classnames('container-fluid full-width dashboard', {'dashboard-edit': isEditMode})}>
{isEditMode ? <Visualizations/> : null} {isEditMode ? <Visualizations/> : null}
{Dashboard.renderDashboard(dashboard, timeRange, source, onPositionChange)} {Dashboard.renderDashboard(dashboard, autoRefresh, timeRange, source, onPositionChange)}
</div> </div>
</div> </div>
) )
} }
Dashboard.renderDashboard = (dashboard, timeRange, source, onPositionChange) => { Dashboard.renderDashboard = (dashboard, autoRefresh, timeRange, source, onPositionChange) => {
const autoRefreshMs = 15000
const cells = dashboard.cells.map((cell, i) => { const cells = dashboard.cells.map((cell, i) => {
i = `${i}` i = `${i}`
const dashboardCell = {...cell, i} const dashboardCell = {...cell, i}
@ -42,7 +42,7 @@ Dashboard.renderDashboard = (dashboard, timeRange, source, onPositionChange) =>
<LayoutRenderer <LayoutRenderer
timeRange={timeRange} timeRange={timeRange}
cells={cells} cells={cells}
autoRefreshMs={autoRefreshMs} autoRefresh={autoRefresh}
source={source.links.proxy} source={source.links.proxy}
onPositionChange={onPositionChange} onPositionChange={onPositionChange}
/> />
@ -54,6 +54,7 @@ const {
func, func,
shape, shape,
string, string,
number,
} = PropTypes } = PropTypes
Dashboard.propTypes = { Dashboard.propTypes = {
@ -66,6 +67,7 @@ Dashboard.propTypes = {
proxy: string, proxy: string,
}).isRequired, }).isRequired,
}).isRequired, }).isRequired,
autoRefresh: number.isRequired,
timeRange: shape({}).isRequired, timeRange: shape({}).isRequired,
} }

View File

@ -2,6 +2,7 @@ import React, {PropTypes} from 'react'
import ReactTooltip from 'react-tooltip' import ReactTooltip from 'react-tooltip'
import {Link} from 'react-router'; import {Link} from 'react-router';
import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from 'shared/components/TimeRangeDropdown' import TimeRangeDropdown from 'shared/components/TimeRangeDropdown'
const DashboardHeader = ({ const DashboardHeader = ({
@ -10,8 +11,10 @@ const DashboardHeader = ({
dashboard, dashboard,
headerText, headerText,
timeRange, timeRange,
autoRefresh,
isHidden, isHidden,
handleChooseTimeRange, handleChooseTimeRange,
handleChooseAutoRefresh,
handleClickPresentationButton, handleClickPresentationButton,
sourceID, sourceID,
}) => isHidden ? null : ( }) => isHidden ? null : (
@ -45,6 +48,7 @@ const DashboardHeader = ({
Graph Tips Graph Tips
</div> </div>
<ReactTooltip id="graph-tips-tooltip" effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip place-bottom" /> <ReactTooltip id="graph-tips-tooltip" effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip place-bottom" />
<AutoRefreshDropdown onChoose={handleChooseAutoRefresh} selected={autoRefresh} iconName="refresh" />
<TimeRangeDropdown onChooseTimeRange={handleChooseTimeRange} selected={timeRange.inputValue} /> <TimeRangeDropdown onChooseTimeRange={handleChooseTimeRange} selected={timeRange.inputValue} />
<div className="btn btn-info btn-sm" onClick={handleClickPresentationButton}> <div className="btn btn-info btn-sm" onClick={handleClickPresentationButton}>
<span className="icon expand-a" style={{margin: 0}}></span> <span className="icon expand-a" style={{margin: 0}}></span>
@ -55,11 +59,12 @@ const DashboardHeader = ({
) )
const { const {
shape,
array, array,
string,
func,
bool, bool,
func,
number,
shape,
string,
} = PropTypes } = PropTypes
DashboardHeader.propTypes = { DashboardHeader.propTypes = {
@ -69,8 +74,10 @@ DashboardHeader.propTypes = {
dashboard: shape({}), dashboard: shape({}),
headerText: string, headerText: string,
timeRange: shape({}).isRequired, timeRange: shape({}).isRequired,
autoRefresh: number.isRequired,
isHidden: bool.isRequired, isHidden: bool.isRequired,
handleChooseTimeRange: func.isRequired, handleChooseTimeRange: func.isRequired,
handleChooseAutoRefresh: func.isRequired,
handleClickPresentationButton: func.isRequired, handleClickPresentationButton: func.isRequired,
} }

View File

@ -51,6 +51,7 @@ const DashboardPage = React.createClass({
id: number.isRequired, id: number.isRequired,
cells: arrayOf(shape({})).isRequired, cells: arrayOf(shape({})).isRequired,
}).isRequired, }).isRequired,
autoRefresh: number.isRequired,
timeRange: shape({}).isRequired, timeRange: shape({}).isRequired,
inPresentationMode: bool.isRequired, inPresentationMode: bool.isRequired,
isEditMode: bool.isRequired, isEditMode: bool.isRequired,
@ -100,6 +101,7 @@ const DashboardPage = React.createClass({
isEditMode, isEditMode,
handleClickPresentationButton, handleClickPresentationButton,
source, source,
autoRefresh,
timeRange, timeRange,
} = this.props } = this.props
@ -110,6 +112,7 @@ const DashboardPage = React.createClass({
<EditHeader dashboard={dashboard} onSave={() => {}} /> : <EditHeader dashboard={dashboard} onSave={() => {}} /> :
<Header <Header
buttonText={dashboard ? dashboard.name : ''} buttonText={dashboard ? dashboard.name : ''}
autoRefresh={autoRefresh}
timeRange={timeRange} timeRange={timeRange}
handleChooseTimeRange={this.handleChooseTimeRange} handleChooseTimeRange={this.handleChooseTimeRange}
isHidden={inPresentationMode} isHidden={inPresentationMode}
@ -133,6 +136,7 @@ const DashboardPage = React.createClass({
isEditMode={isEditMode} isEditMode={isEditMode}
inPresentationMode={inPresentationMode} inPresentationMode={inPresentationMode}
source={source} source={source}
autoRefresh={autoRefresh}
timeRange={timeRange} timeRange={timeRange}
onPositionChange={this.handleUpdatePosition} onPositionChange={this.handleUpdatePosition}
/> />
@ -143,7 +147,10 @@ const DashboardPage = React.createClass({
const mapStateToProps = (state) => { const mapStateToProps = (state) => {
const { const {
appUI, app: {
ephemeral: {inPresentationMode},
persisted: {autoRefresh},
},
dashboardUI: { dashboardUI: {
dashboards, dashboards,
dashboard, dashboard,
@ -153,11 +160,12 @@ const mapStateToProps = (state) => {
} = state } = state
return { return {
inPresentationMode: appUI.presentationMode,
dashboards, dashboards,
dashboard, dashboard,
autoRefresh,
timeRange, timeRange,
isEditMode, isEditMode,
inPresentationMode,
} }
} }

View File

@ -15,6 +15,7 @@ const {
const Visualization = React.createClass({ const Visualization = React.createClass({
propTypes: { propTypes: {
autoRefresh: number.isRequired,
timeRange: shape({ timeRange: shape({
upper: string, upper: string,
lower: string, lower: string,
@ -45,7 +46,7 @@ const Visualization = React.createClass({
}, },
render() { render() {
const {queryConfigs, timeRange, activeQueryIndex, height, heightPixels} = this.props; const {queryConfigs, autoRefresh, timeRange, activeQueryIndex, height, heightPixels} = this.props;
const {source} = this.context; const {source} = this.context;
const proxyLink = source.links.proxy; const proxyLink = source.links.proxy;
@ -57,7 +58,6 @@ const Visualization = React.createClass({
const queries = statements.filter((s) => s.text !== null).map((s) => { const queries = statements.filter((s) => s.text !== null).map((s) => {
return {host: [proxyLink], text: s.text, id: s.id}; return {host: [proxyLink], text: s.text, id: s.id};
}); });
const autoRefreshMs = 10000;
const isInDataExplorer = true; const isInDataExplorer = true;
return ( return (
@ -77,7 +77,7 @@ const Visualization = React.createClass({
{isGraphInView ? ( {isGraphInView ? (
<RefreshingLineGraph <RefreshingLineGraph
queries={queries} queries={queries}
autoRefresh={autoRefreshMs} autoRefresh={autoRefresh}
activeQueryIndex={activeQueryIndex} activeQueryIndex={activeQueryIndex}
isInDataExplorer={isInDataExplorer} isInDataExplorer={isInDataExplorer}
/> />

View File

@ -1,17 +1,18 @@
import React, {PropTypes} from 'react'; import React, {PropTypes} from 'react';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import QueryBuilder from '../components/QueryBuilder'; import QueryBuilder from '../components/QueryBuilder';
import Visualization from '../components/Visualization'; import Visualization from '../components/Visualization';
import Header from '../containers/Header'; import Header from '../containers/Header';
import ResizeContainer from 'src/shared/components/ResizeContainer'; import ResizeContainer from 'src/shared/components/ResizeContainer';
import { import {setAutoRefresh} from 'shared/actions/app'
setTimeRange as setTimeRangeAction, import {setTimeRange as setTimeRangeAction} from '../actions/view';
} from '../actions/view';
const { const {
arrayOf, arrayOf,
func, func,
number,
shape, shape,
string, string,
} = PropTypes; } = PropTypes;
@ -25,6 +26,8 @@ const DataExplorer = React.createClass({
}).isRequired, }).isRequired,
}).isRequired, }).isRequired,
queryConfigs: PropTypes.shape({}), queryConfigs: PropTypes.shape({}),
autoRefresh: number.isRequired,
handleChooseAutoRefresh: func.isRequired,
timeRange: shape({ timeRange: shape({
upper: string, upper: string,
lower: string, lower: string,
@ -59,18 +62,20 @@ const DataExplorer = React.createClass({
}, },
render() { render() {
const {timeRange, setTimeRange, queryConfigs, dataExplorer} = this.props; const {autoRefresh, handleChooseAutoRefresh, timeRange, setTimeRange, queryConfigs, dataExplorer} = this.props;
const {activeQueryID} = this.state; const {activeQueryID} = this.state;
const queries = dataExplorer.queryIDs.map((qid) => queryConfigs[qid]); const queries = dataExplorer.queryIDs.map((qid) => queryConfigs[qid]);
return ( return (
<div className="data-explorer"> <div className="data-explorer">
<Header <Header
actions={{setTimeRange}} actions={{handleChooseAutoRefresh, setTimeRange}}
autoRefresh={autoRefresh}
timeRange={timeRange} timeRange={timeRange}
/> />
<ResizeContainer> <ResizeContainer>
<Visualization <Visualization
autoRefresh={autoRefresh}
timeRange={timeRange} timeRange={timeRange}
queryConfigs={queries} queryConfigs={queries}
activeQueryID={this.state.activeQueryID} activeQueryID={this.state.activeQueryID}
@ -78,6 +83,7 @@ const DataExplorer = React.createClass({
/> />
<QueryBuilder <QueryBuilder
queries={queries} queries={queries}
autoRefresh={autoRefresh}
timeRange={timeRange} timeRange={timeRange}
setActiveQuery={this.handleSetActiveQuery} setActiveQuery={this.handleSetActiveQuery}
activeQueryID={activeQueryID} activeQueryID={activeQueryID}
@ -89,15 +95,21 @@ const DataExplorer = React.createClass({
}); });
function mapStateToProps(state) { function mapStateToProps(state) {
const {timeRange, queryConfigs, dataExplorer} = state; const {app: {persisted: {autoRefresh}}, timeRange, queryConfigs, dataExplorer} = state;
return { return {
autoRefresh,
timeRange, timeRange,
queryConfigs, queryConfigs,
dataExplorer, dataExplorer,
}; };
} }
export default connect(mapStateToProps, { function mapDispatchToProps(dispatch) {
setTimeRange: setTimeRangeAction, return {
})(DataExplorer); handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
setTimeRange: bindActionCreators(setTimeRangeAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DataExplorer);

View File

@ -1,23 +1,35 @@
import React, {PropTypes} from 'react'; import React, {PropTypes} from 'react';
import moment from 'moment'; import moment from 'moment';
import {withRouter} from 'react-router'; import {withRouter} from 'react-router';
import AutoRefreshDropdown from 'shared/components/AutoRefreshDropdown'
import TimeRangeDropdown from '../../shared/components/TimeRangeDropdown'; import TimeRangeDropdown from '../../shared/components/TimeRangeDropdown';
import timeRanges from 'hson!../../shared/data/timeRanges.hson'; import timeRanges from 'hson!../../shared/data/timeRanges.hson';
const {
func,
number,
shape,
string,
} = PropTypes
const Header = React.createClass({ const Header = React.createClass({
propTypes: { propTypes: {
timeRange: PropTypes.shape({ autoRefresh: number.isRequired,
upper: PropTypes.string, timeRange: shape({
lower: PropTypes.string, upper: string,
lower: string,
}).isRequired, }).isRequired,
actions: PropTypes.shape({ actions: shape({
setTimeRange: PropTypes.func.isRequired, handleChooseAutoRefresh: func.isRequired,
setTimeRange: func.isRequired,
}), }),
}, },
contextTypes: { contextTypes: {
source: PropTypes.shape({ source: shape({
name: PropTypes.string, name: string,
}), }),
}, },
@ -36,7 +48,7 @@ const Header = React.createClass({
}, },
render() { render() {
const {timeRange} = this.props; const {autoRefresh, actions: {handleChooseAutoRefresh}, timeRange} = this.props;
return ( return (
<div className="page-header"> <div className="page-header">
@ -50,6 +62,7 @@ const Header = React.createClass({
<span className="icon cpu"></span> <span className="icon cpu"></span>
{this.context.source.name} {this.context.source.name}
</div> </div>
<AutoRefreshDropdown onChoose={handleChooseAutoRefresh} selected={autoRefresh} iconName="refresh" />
<TimeRangeDropdown onChooseTimeRange={this.handleChooseTimeRange} selected={this.findSelected(timeRange)} /> <TimeRangeDropdown onChooseTimeRange={this.handleChooseTimeRange} selected={this.findSelected(timeRange)} />
</div> </div>
</div> </div>

View File

@ -2,8 +2,8 @@ import queryConfigs from './queryConfigs';
import timeRange from './timeRange'; import timeRange from './timeRange';
import dataExplorer from './ui'; import dataExplorer from './ui';
export { export default {
queryConfigs, queryConfigs,
timeRange, timeRange,
dataExplorer, dataExplorer,
}; }

View File

@ -1,6 +1,7 @@
import React, {PropTypes} from 'react' import React, {PropTypes} from 'react'
import {Link} from 'react-router' import {Link} from 'react-router'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import _ from 'lodash' import _ from 'lodash'
import classnames from 'classnames'; import classnames from 'classnames';
@ -9,6 +10,8 @@ import DashboardHeader from 'src/dashboards/components/DashboardHeader';
import timeRanges from 'hson!../../shared/data/timeRanges.hson'; import timeRanges from 'hson!../../shared/data/timeRanges.hson';
import {getMappings, getAppsForHosts, getMeasurementsForHost, getAllHosts} from 'src/hosts/apis'; import {getMappings, getAppsForHosts, getMeasurementsForHost, getAllHosts} from 'src/hosts/apis';
import {fetchLayouts} from 'shared/apis'; import {fetchLayouts} from 'shared/apis';
import {setAutoRefresh} from 'shared/actions/app'
import {presentationButtonDispatcher} from 'shared/dispatchers' import {presentationButtonDispatcher} from 'shared/dispatchers'
const { const {
@ -16,6 +19,7 @@ const {
string, string,
bool, bool,
func, func,
number,
} = PropTypes } = PropTypes
export const HostPage = React.createClass({ export const HostPage = React.createClass({
@ -35,6 +39,8 @@ export const HostPage = React.createClass({
app: string, app: string,
}), }),
}), }),
autoRefresh: number.isRequired,
handleChooseAutoRefresh: func.isRequired,
inPresentationMode: bool, inPresentationMode: bool,
handleClickPresentationButton: func, handleClickPresentationButton: func,
}, },
@ -87,9 +93,8 @@ export const HostPage = React.createClass({
}, },
renderLayouts(layouts) { renderLayouts(layouts) {
const autoRefreshMs = 15000;
const {timeRange} = this.state; const {timeRange} = this.state;
const {source} = this.props; const {source, autoRefresh} = this.props;
const autoflowLayouts = layouts.filter((layout) => !!layout.autoflow); const autoflowLayouts = layouts.filter((layout) => !!layout.autoflow);
@ -137,7 +142,7 @@ export const HostPage = React.createClass({
<LayoutRenderer <LayoutRenderer
timeRange={timeRange} timeRange={timeRange}
cells={layoutCells} cells={layoutCells}
autoRefreshMs={autoRefreshMs} autoRefresh={autoRefresh}
source={source.links.proxy} source={source.links.proxy}
host={this.props.params.hostID} host={this.props.params.hostID}
/> />
@ -145,7 +150,7 @@ export const HostPage = React.createClass({
}, },
render() { render() {
const {params: {hostID}, location: {query: {app}}, source: {id}, inPresentationMode, handleClickPresentationButton} = this.props const {params: {hostID}, location: {query: {app}}, source: {id}, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton} = this.props
const {layouts, timeRange, hosts} = this.state const {layouts, timeRange, hosts} = this.state
const appParam = app ? `?app=${app}` : '' const appParam = app ? `?app=${app}` : ''
@ -153,9 +158,11 @@ export const HostPage = React.createClass({
<div className="page"> <div className="page">
<DashboardHeader <DashboardHeader
buttonText={hostID} buttonText={hostID}
autoRefresh={autoRefresh}
timeRange={timeRange} timeRange={timeRange}
isHidden={inPresentationMode} isHidden={inPresentationMode}
handleChooseTimeRange={this.handleChooseTimeRange} handleChooseTimeRange={this.handleChooseTimeRange}
handleChooseAutoRefresh={handleChooseAutoRefresh}
handleClickPresentationButton={handleClickPresentationButton} handleClickPresentationButton={handleClickPresentationButton}
> >
{Object.keys(hosts).map((host, i) => { {Object.keys(hosts).map((host, i) => {
@ -181,11 +188,13 @@ export const HostPage = React.createClass({
}, },
}); });
const mapStateToProps = (state) => ({ const mapStateToProps = ({app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}}}) => ({
inPresentationMode: state.appUI.presentationMode, inPresentationMode,
autoRefresh,
}) })
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
handleClickPresentationButton: presentationButtonDispatcher(dispatch), handleClickPresentationButton: presentationButtonDispatcher(dispatch),
}) })

View File

@ -19,7 +19,7 @@ import configureStore from 'src/store/configureStore';
import {getMe, getSources} from 'shared/apis'; import {getMe, getSources} from 'shared/apis';
import {receiveMe} from 'shared/actions/me'; import {receiveMe} from 'shared/actions/me';
import {receiveAuth} from 'shared/actions/auth'; import {receiveAuth} from 'shared/actions/auth';
import {disablePresentationMode} from 'shared/actions/ui'; import {disablePresentationMode} from 'shared/actions/app';
import {loadLocalStorage} from './localStorage'; import {loadLocalStorage} from './localStorage';
import 'src/style/chronograf.scss'; import 'src/style/chronograf.scss';

View File

@ -6,11 +6,12 @@ import DashboardHeader from 'src/dashboards/components/DashboardHeader';
import timeRanges from 'hson!../../shared/data/timeRanges.hson'; import timeRanges from 'hson!../../shared/data/timeRanges.hson';
const { const {
shape,
string,
arrayOf, arrayOf,
bool, bool,
func, func,
number,
shape,
string,
} = PropTypes } = PropTypes
export const KubernetesDashboard = React.createClass({ export const KubernetesDashboard = React.createClass({
@ -22,6 +23,8 @@ export const KubernetesDashboard = React.createClass({
telegraf: string.isRequired, telegraf: string.isRequired,
}), }),
layouts: arrayOf(shape().isRequired).isRequired, layouts: arrayOf(shape().isRequired).isRequired,
autoRefresh: number.isRequired,
handleChooseAutoRefresh: func.isRequired,
inPresentationMode: bool.isRequired, inPresentationMode: bool.isRequired,
handleClickPresentationButton: func, handleClickPresentationButton: func,
}, },
@ -34,9 +37,8 @@ export const KubernetesDashboard = React.createClass({
}, },
renderLayouts(layouts) { renderLayouts(layouts) {
const autoRefreshMs = 15000;
const {timeRange} = this.state; const {timeRange} = this.state;
const {source} = this.props; const {source, autoRefresh} = this.props;
let layoutCells = []; let layoutCells = [];
layouts.forEach((layout) => { layouts.forEach((layout) => {
@ -56,7 +58,7 @@ export const KubernetesDashboard = React.createClass({
<LayoutRenderer <LayoutRenderer
timeRange={timeRange} timeRange={timeRange}
cells={layoutCells} cells={layoutCells}
autoRefreshMs={autoRefreshMs} autoRefresh={autoRefresh}
source={source.links.proxy} source={source.links.proxy}
/> />
); );
@ -68,7 +70,7 @@ export const KubernetesDashboard = React.createClass({
}, },
render() { render() {
const {layouts, inPresentationMode, handleClickPresentationButton} = this.props; const {layouts, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton} = this.props;
const {timeRange} = this.state; const {timeRange} = this.state;
const emptyState = ( const emptyState = (
<div className="generic-empty-state"> <div className="generic-empty-state">
@ -81,6 +83,8 @@ export const KubernetesDashboard = React.createClass({
<div className="page"> <div className="page">
<DashboardHeader <DashboardHeader
headerText="Kubernetes Dashboard" headerText="Kubernetes Dashboard"
autoRefresh={autoRefresh}
handleChooseAutoRefresh={handleChooseAutoRefresh}
timeRange={timeRange} timeRange={timeRange}
handleChooseTimeRange={this.handleChooseTimeRange} handleChooseTimeRange={this.handleChooseTimeRange}
isHidden={inPresentationMode} isHidden={inPresentationMode}

View File

@ -1,15 +1,19 @@
import React, {PropTypes} from 'react'; import React, {PropTypes} from 'react';
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {fetchLayouts} from 'shared/apis'; import {fetchLayouts} from 'shared/apis';
import KubernetesDashboard from 'src/kubernetes/components/KubernetesDashboard'; import KubernetesDashboard from 'src/kubernetes/components/KubernetesDashboard';
import {setAutoRefresh} from 'shared/actions/app'
import {presentationButtonDispatcher} from 'shared/dispatchers' import {presentationButtonDispatcher} from 'shared/dispatchers'
const { const {
shape,
string,
bool, bool,
func, func,
number,
shape,
string,
} = PropTypes } = PropTypes
export const KubernetesPage = React.createClass({ export const KubernetesPage = React.createClass({
@ -19,6 +23,8 @@ export const KubernetesPage = React.createClass({
proxy: string.isRequired, proxy: string.isRequired,
}).isRequired, }).isRequired,
}), }),
autoRefresh: number.isRequired,
handleChooseAutoRefresh: func.isRequired,
inPresentationMode: bool.isRequired, inPresentationMode: bool.isRequired,
handleClickPresentationButton: func, handleClickPresentationButton: func,
}, },
@ -38,12 +44,14 @@ export const KubernetesPage = React.createClass({
render() { render() {
const {layouts} = this.state const {layouts} = this.state
const {source, inPresentationMode, handleClickPresentationButton} = this.props const {source, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton} = this.props
return ( return (
<KubernetesDashboard <KubernetesDashboard
layouts={layouts} layouts={layouts}
source={source} source={source}
autoRefresh={autoRefresh}
handleChooseAutoRefresh={handleChooseAutoRefresh}
inPresentationMode={inPresentationMode} inPresentationMode={inPresentationMode}
handleClickPresentationButton={handleClickPresentationButton} handleClickPresentationButton={handleClickPresentationButton}
/> />
@ -51,11 +59,13 @@ export const KubernetesPage = React.createClass({
}, },
}); });
const mapStateToProps = (state) => ({ const mapStateToProps = ({app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}}}) => ({
inPresentationMode: state.appUI.presentationMode, inPresentationMode,
autoRefresh,
}) })
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
handleClickPresentationButton: presentationButtonDispatcher(dispatch), handleClickPresentationButton: presentationButtonDispatcher(dispatch),
}) })

View File

@ -9,9 +9,12 @@ export const loadLocalStorage = () => {
} }
}; };
export const saveToLocalStorage = ({queryConfigs, timeRange, dataExplorer}) => { export const saveToLocalStorage = ({app: {persisted}, queryConfigs, timeRange, dataExplorer}) => {
try { try {
const appPersisted = Object.assign({}, {app: {persisted}})
window.localStorage.setItem('state', JSON.stringify({ window.localStorage.setItem('state', JSON.stringify({
...appPersisted,
queryConfigs, queryConfigs,
timeRange, timeRange,
dataExplorer, dataExplorer,

View File

@ -0,0 +1,22 @@
import {PRESENTATION_MODE_ANIMATION_DELAY} from '../constants'
// ephemeral state reducers
export const enablePresentationMode = () => ({
type: 'ENABLE_PRESENTATION_MODE',
})
export const disablePresentationMode = () => ({
type: 'DISABLE_PRESENTATION_MODE',
})
export const delayEnablePresentationMode = () => (dispatch) => {
setTimeout(() => dispatch(enablePresentationMode()), PRESENTATION_MODE_ANIMATION_DELAY)
}
// persistent state reducers
export const setAutoRefresh = (milliseconds) => ({
type: 'SET_AUTOREFRESH',
payload: {
milliseconds,
},
})

View File

@ -1,19 +0,0 @@
import {PRESENTATION_MODE_ANIMATION_DELAY} from '../constants'
export function enablePresentationMode() {
return {
type: 'ENABLE_PRESENTATION_MODE',
}
}
export function disablePresentationMode() {
return {
type: 'DISABLE_PRESENTATION_MODE',
}
}
export function delayEnablePresentationMode() {
return (dispatch) => {
setTimeout(() => dispatch(enablePresentationMode()), PRESENTATION_MODE_ANIMATION_DELAY)
}
}

View File

@ -6,25 +6,34 @@ function _fetchTimeSeries(source, db, rp, query) {
return proxy({source, db, rp, query}); return proxy({source, db, rp, query});
} }
const {
element,
number,
arrayOf,
shape,
oneOfType,
string,
} = PropTypes
export default function AutoRefresh(ComposedComponent) { export default function AutoRefresh(ComposedComponent) {
const wrapper = React.createClass({ const wrapper = React.createClass({
displayName: `AutoRefresh_${ComposedComponent.displayName}`, displayName: `AutoRefresh_${ComposedComponent.displayName}`,
propTypes: { propTypes: {
children: PropTypes.element, children: element,
autoRefresh: PropTypes.number, autoRefresh: number.isRequired,
queries: PropTypes.arrayOf(PropTypes.shape({ queries: arrayOf(shape({
host: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]), host: oneOfType([string, arrayOf(string)]),
text: PropTypes.string, text: string,
}).isRequired).isRequired, }).isRequired).isRequired,
}, },
getInitialState() { getInitialState() {
return {timeSeries: []}; return {timeSeries: []};
}, },
componentDidMount() { componentDidMount() {
const {queries} = this.props; const {queries, autoRefresh} = this.props;
this.executeQueries(queries); this.executeQueries(queries);
if (this.props.autoRefresh) { if (autoRefresh) {
this.intervalID = setInterval(() => this.executeQueries(queries), this.props.autoRefresh); this.intervalID = setInterval(() => this.executeQueries(queries), autoRefresh);
} }
}, },
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {

View File

@ -1,14 +1,23 @@
import React, {PropTypes} from 'react'; import React, {PropTypes} from 'react';
import classnames from 'classnames';
import OnClickOutside from 'shared/components/OnClickOutside'; import OnClickOutside from 'shared/components/OnClickOutside';
const {
arrayOf,
shape,
string,
func,
} = PropTypes
const Dropdown = React.createClass({ const Dropdown = React.createClass({
propTypes: { propTypes: {
items: PropTypes.arrayOf(PropTypes.shape({ items: arrayOf(shape({
text: PropTypes.string.isRequired, text: string.isRequired,
})).isRequired, })).isRequired,
onChoose: PropTypes.func.isRequired, onChoose: func.isRequired,
selected: PropTypes.string.isRequired, selected: string.isRequired,
className: PropTypes.string, iconName: string,
className: string,
}, },
getInitialState() { getInitialState() {
return { return {
@ -39,11 +48,12 @@ const Dropdown = React.createClass({
}, },
render() { render() {
const self = this; const self = this;
const {items, selected, className, actions} = self.props; const {items, selected, className, iconName, actions} = self.props;
return ( return (
<div onClick={this.toggleMenu} className={`dropdown ${className}`}> <div onClick={this.toggleMenu} className={`dropdown ${className}`}>
<div className="btn btn-sm btn-info dropdown-toggle"> <div className="btn btn-sm btn-info dropdown-toggle">
{iconName ? <span className={classnames("icon", {[iconName]: true})}></span> : null}
<span className="dropdown-selected">{selected}</span> <span className="dropdown-selected">{selected}</span>
<span className="caret" /> <span className="caret" />
</div> </div>

View File

@ -18,6 +18,7 @@ const {
export const LayoutRenderer = React.createClass({ export const LayoutRenderer = React.createClass({
propTypes: { propTypes: {
autoRefresh: number.isRequired,
timeRange: shape({ timeRange: shape({
defaultGroupBy: string.isRequired, defaultGroupBy: string.isRequired,
queryValue: string.isRequired, queryValue: string.isRequired,
@ -46,7 +47,6 @@ export const LayoutRenderer = React.createClass({
name: string.isRequired, name: string.isRequired,
}).isRequired }).isRequired
), ),
autoRefreshMs: number.isRequired,
host: string, host: string,
source: string, source: string,
onPositionChange: func, onPositionChange: func,
@ -84,7 +84,7 @@ export const LayoutRenderer = React.createClass({
}, },
generateVisualizations() { generateVisualizations() {
const {autoRefreshMs, source, cells} = this.props; const {autoRefresh, source, cells} = this.props;
return cells.map((cell) => { return cells.map((cell) => {
const qs = cell.queries.map((q) => { const qs = cell.queries.map((q) => {
@ -100,7 +100,7 @@ export const LayoutRenderer = React.createClass({
<div key={cell.i}> <div key={cell.i}>
<h2 className="dash-graph--heading">{cell.name || `Graph`}</h2> <h2 className="dash-graph--heading">{cell.name || `Graph`}</h2>
<div className="dash-graph--container"> <div className="dash-graph--container">
<RefreshingSingleStat queries={[qs[0]]} autoRefresh={autoRefreshMs} /> <RefreshingSingleStat queries={[qs[0]]} autoRefresh={autoRefresh} />
</div> </div>
</div> </div>
); );
@ -117,7 +117,7 @@ export const LayoutRenderer = React.createClass({
<div className="dash-graph--container"> <div className="dash-graph--container">
<RefreshingLineGraph <RefreshingLineGraph
queries={qs} queries={qs}
autoRefresh={autoRefreshMs} autoRefresh={autoRefresh}
showSingleStat={cell.type === "line-plus-single-stat"} showSingleStat={cell.type === "line-plus-single-stat"}
displayOptions={displayOptions} displayOptions={displayOptions}
/> />

View File

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import cN from 'classnames'; import classnames from 'classnames';
import OnClickOutside from 'shared/components/OnClickOutside'; import OnClickOutside from 'shared/components/OnClickOutside';
import timeRanges from 'hson!../data/timeRanges.hson'; import timeRanges from 'hson!../data/timeRanges.hson';
@ -48,7 +48,7 @@ const TimeRangeDropdown = React.createClass({
<span className="selected-time-range">{selected}</span> <span className="selected-time-range">{selected}</span>
<span className="caret" /> <span className="caret" />
</div> </div>
<ul className={cN("dropdown-menu", {show: isOpen})}> <ul className={classnames("dropdown-menu", {show: isOpen})}>
<li className="dropdown-header">Time Range</li> <li className="dropdown-header">Time Range</li>
{timeRanges.map((item) => { {timeRanges.map((item) => {
return ( return (

View File

@ -470,3 +470,5 @@ export const STROKE_WIDTH = {
export const PRESENTATION_MODE_ANIMATION_DELAY = 0 // In milliseconds. export const PRESENTATION_MODE_ANIMATION_DELAY = 0 // In milliseconds.
export const PRESENTATION_MODE_NOTIFICATION_DELAY = 2000 // In milliseconds. export const PRESENTATION_MODE_NOTIFICATION_DELAY = 2000 // In milliseconds.
export const AUTOREFRESH_DEFAULT = 15000 // in milliseconds

View File

@ -1,4 +1,4 @@
import {delayEnablePresentationMode} from 'shared/actions/ui' import {delayEnablePresentationMode} from 'shared/actions/app'
import {publishNotification, delayDismissNotification} from 'shared/actions/notifications' import {publishNotification, delayDismissNotification} from 'shared/actions/notifications'
import {PRESENTATION_MODE_NOTIFICATION_DELAY} from 'shared/constants' import {PRESENTATION_MODE_NOTIFICATION_DELAY} from 'shared/constants'

View File

@ -0,0 +1,57 @@
import {combineReducers} from 'redux';
import {AUTOREFRESH_DEFAULT} from 'src/shared/constants'
const initialState = {
ephemeral: {
inPresentationMode: false,
},
persisted: {
autoRefresh: AUTOREFRESH_DEFAULT,
},
}
const {
ephemeral: initialEphemeralState,
persisted: initialPersistedState,
} = initialState
const ephemeralReducer = (state = initialEphemeralState, action) => {
switch (action.type) {
case 'ENABLE_PRESENTATION_MODE': {
return {
...state,
inPresentationMode: true,
}
}
case 'DISABLE_PRESENTATION_MODE': {
return {
...state,
inPresentationMode: false,
}
}
default:
return state
}
}
const persistedReducer = (state = initialPersistedState, action) => {
switch (action.type) {
case 'SET_AUTOREFRESH': {
return {
...state,
autoRefresh: action.payload.milliseconds,
}
}
default:
return state
}
}
export default combineReducers({
ephemeral: ephemeralReducer,
persisted: persistedReducer,
})

View File

@ -1,13 +1,13 @@
import appUI from './ui';
import me from './me'; import me from './me';
import app from './app';
import auth from './auth'; import auth from './auth';
import notifications from './notifications'; import notifications from './notifications';
import sources from './sources'; import sources from './sources';
export { export default {
appUI,
me, me,
app,
auth, auth,
notifications, notifications,
sources, sources,
}; }

View File

@ -1,23 +0,0 @@
const initialState = {
presentationMode: false,
};
export default function ui(state = initialState, action) {
switch (action.type) {
case 'ENABLE_PRESENTATION_MODE': {
return {
...state,
presentationMode: true,
}
}
case 'DISABLE_PRESENTATION_MODE': {
return {
...state,
presentationMode: false,
}
}
}
return state
}

View File

@ -34,11 +34,9 @@ const SideNavApp = React.createClass({
}, },
}); });
function mapStateToProps(state) { const mapStateToProps = ({me, app: {ephemeral: {inPresentationMode}}}) => ({
return { me,
me: state.me, inPresentationMode,
inPresentationMode: state.appUI.presentationMode, })
};
}
export default connect(mapStateToProps)(SideNavApp); export default connect(mapStateToProps)(SideNavApp);

View File

@ -3,8 +3,8 @@ import {combineReducers} from 'redux';
import thunkMiddleware from 'redux-thunk'; import thunkMiddleware from 'redux-thunk';
import makeQueryExecuter from 'src/shared/middleware/queryExecuter'; import makeQueryExecuter from 'src/shared/middleware/queryExecuter';
import resizeLayout from 'src/shared/middleware/resizeLayout'; import resizeLayout from 'src/shared/middleware/resizeLayout';
import * as dataExplorerReducers from 'src/data_explorer/reducers'; import sharedReducers from 'src/shared/reducers';
import * as sharedReducers from 'src/shared/reducers'; import dataExplorerReducers from 'src/data_explorer/reducers';
import rulesReducer from 'src/kapacitor/reducers/rules'; import rulesReducer from 'src/kapacitor/reducers/rules';
import dashboardUI from 'src/dashboards/reducers/ui'; import dashboardUI from 'src/dashboards/reducers/ui';
import persistStateEnhancer from './persistStateEnhancer'; import persistStateEnhancer from './persistStateEnhancer';