Merge branch 'master' into chore/alphabetical-dbs

pull/10616/head
Andrew Watkins 2017-04-28 12:47:29 -07:00 committed by GitHub
commit 18aad4b563
12 changed files with 186 additions and 281 deletions

View File

@ -2,12 +2,17 @@
### Bug Fixes
1. [#1337](https://github.com/influxdata/chronograf/pull/1337): Fix no apps for hosts false negative
1. [#1340](https://github.com/influxdata/chronograf/pull/1340): Fix no active query in DE and Cell editing
1. [#1338](https://github.com/influxdata/chronograf/pull/1338): Require url and name when adding a new source
1. [#1348](https://github.com/influxdata/chronograf/pull/1348): Fix broken 'Add Kapacitor' Link
### Features
### UI Improvements
1. [#1335](https://github.com/influxdata/chronograf/pull/1335): Improve UX for sanitized kapacitor settings
1. [#1342](https://github.com/influxdata/chronograf/pull/1342): No more sort-as-you-type in DB admin
1. [#1344](https://github.com/influxdata/chronograf/pull/1344): Remove K8 dashboard
1. [#1340](https://github.com/influxdata/chronograf/pull/1340): Automatically switch to table view if meta query
## v1.2.0-beta9 [2017-04-21]
@ -17,6 +22,8 @@
1. [#1269](https://github.com/influxdata/chronograf/issues/1269): Add more functionality to the explorer's query generation process
1. [#1318](https://github.com/influxdata/chronograf/issues/1318): Fix JWT refresh for auth-durations of zero and less than five minutes
1. [#1332](https://github.com/influxdata/chronograf/pull/1332): Remove table toggle from dashboard visualization
1. [#1335](https://github.com/influxdata/chronograf/pull/1335): Improve UX for sanitized kapacitor settings
### Features
1. [#1232](https://github.com/influxdata/chronograf/pull/1232): Fuse the query builder and raw query editor

View File

@ -32,11 +32,8 @@ const DashboardHeader = ({
<ul className="dropdown-menu" aria-labelledby="dropdownMenu1">
{children}
</ul>
</div>
}
{headerText &&
<h1>Kubernetes Dashboard</h1>
}
</div>}
{headerText}
</div>
<div className="page-header__right">
<GraphTips />

View File

@ -11,12 +11,7 @@ import {getDashboardsAsync, deleteDashboardAsync} from 'src/dashboards/actions'
import {NEW_DASHBOARD} from 'src/dashboards/constants'
const {
arrayOf,
func,
string,
shape,
} = PropTypes
const {arrayOf, func, string, shape} = PropTypes
const DashboardsPage = React.createClass({
propTypes: {
@ -57,10 +52,10 @@ const DashboardsPage = React.createClass({
let tableHeader
if (dashboards === null) {
tableHeader = 'Loading Dashboards...'
} else if (dashboards.length === 0) {
} else if (dashboards.length === 1) {
tableHeader = '1 Dashboard'
} else {
tableHeader = `${dashboards.length + 1} Dashboards`
tableHeader = `${dashboards.length} Dashboards`
}
return (
@ -84,43 +79,52 @@ const DashboardsPage = React.createClass({
<div className="panel panel-minimal">
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
<h2 className="panel-title">{tableHeader}</h2>
<button className="btn btn-sm btn-primary" onClick={this.handleCreateDashbord}>Create Dashboard</button>
<button
className="btn btn-sm btn-primary"
onClick={this.handleCreateDashbord}
>
Create Dashboard
</button>
</div>
<div className="panel-body">
<table className="table v-center admin-table">
<thead>
<tr>
<th>Name</th>
<th></th>
</tr>
</thead>
<tbody>
{
dashboards && dashboards.length ?
dashboards.map((dashboard) => {
return (
<tr key={dashboard.id} className="">
<td className="monotype">
<Link to={`${dashboardLink}/dashboards/${dashboard.id}`}>
{dashboard.name}
</Link>
</td>
<DeleteConfirmTableCell onDelete={this.handleDeleteDashboard} item={dashboard} />
</tr>
)
}) :
null
}
<tr>
<td className="monotype">
<Link to={`${dashboardLink}/kubernetes`}>
{'Kubernetes'}
</Link>
</td>
<td></td>
</tr>
</tbody>
</table>
{dashboards && dashboards.length
? <table className="table v-center admin-table">
<thead>
<tr>
<th>Name</th>
<th />
</tr>
</thead>
<tbody>
{dashboards.map(dashboard => (
<tr key={dashboard.id} className="">
<td className="monotype">
<Link
to={`${dashboardLink}/dashboards/${dashboard.id}`}
>
{dashboard.name}
</Link>
</td>
<DeleteConfirmTableCell
onDelete={this.handleDeleteDashboard}
item={dashboard}
/>
</tr>
))}
</tbody>
</table>
: <div className="generic-empty-state">
<h4 style={{marginTop: '90px'}}>
Looks like you dont have any dashboards
</h4>
<button
className="btn btn-sm btn-primary"
onClick={this.handleCreateDashbord}
style={{marginBottom: '90px'}}
>
Create Dashboard
</button>
</div>}
</div>
</div>
</div>
@ -137,9 +141,11 @@ const mapStateToProps = ({dashboardUI: {dashboards, dashboard}}) => ({
dashboard,
})
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = dispatch => ({
handleGetDashboards: bindActionCreators(getDashboardsAsync, dispatch),
handleDeleteDashboard: bindActionCreators(deleteDashboardAsync, dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(DashboardsPage))
export default connect(mapStateToProps, mapDispatchToProps)(
withRouter(DashboardsPage)
)

View File

@ -4,8 +4,10 @@ import classNames from 'classnames'
import VisHeader from 'src/data_explorer/components/VisHeader'
import VisView from 'src/data_explorer/components/VisView'
import {GRAPH, TABLE} from 'src/shared/constants'
import _ from 'lodash'
const {arrayOf, func, number, shape, string} = PropTypes
const META_QUERY_REGEX = /^show/i
const Visualization = React.createClass({
propTypes: {
@ -33,18 +35,12 @@ const Visualization = React.createClass({
},
getInitialState() {
const {queryConfigs, activeQueryIndex} = this.props
if (!queryConfigs.length || activeQueryIndex === null) {
return {
view: GRAPH,
}
}
const {activeQueryIndex, queryConfigs} = this.props
const activeQueryText = this.getQueryText(queryConfigs, activeQueryIndex)
return {
view: typeof queryConfigs[activeQueryIndex].rawText === 'string'
? TABLE
: GRAPH,
}
return activeQueryText.match(META_QUERY_REGEX)
? {view: TABLE}
: {view: GRAPH}
},
getDefaultProps() {
@ -54,19 +50,22 @@ const Visualization = React.createClass({
},
componentWillReceiveProps(nextProps) {
const {queryConfigs, activeQueryIndex} = nextProps
if (
!queryConfigs.length ||
activeQueryIndex === null ||
activeQueryIndex === this.props.activeQueryIndex
) {
const {activeQueryIndex, queryConfigs} = nextProps
const nextQueryText = this.getQueryText(queryConfigs, activeQueryIndex)
const queryText = this.getQueryText(
this.props.queryConfigs,
this.props.activeQueryIndex
)
if (queryText === nextQueryText) {
return
}
const activeQuery = queryConfigs[activeQueryIndex]
if (activeQuery && typeof activeQuery.rawText === 'string') {
if (nextQueryText.match(META_QUERY_REGEX)) {
return this.setState({view: TABLE})
}
this.setState({view: GRAPH})
},
handleToggleView(view) {
@ -125,6 +124,11 @@ const Visualization = React.createClass({
</div>
)
},
getQueryText(queryConfigs, index) {
// rawText can be null
return _.get(queryConfigs, [`${index}`, 'rawText'], '') || ''
},
})
export default Visualization

View File

@ -57,7 +57,7 @@ const DataExplorer = React.createClass({
getInitialState() {
return {
activeQueryIndex: null,
activeQueryIndex: 0,
}
},

View File

@ -9,7 +9,6 @@ import App from 'src/App'
import AlertsApp from 'src/alerts'
import CheckSources from 'src/CheckSources'
import {HostsPage, HostPage} from 'src/hosts'
import {KubernetesPage} from 'src/kubernetes'
import {Login, UserIsAuthenticated, UserIsNotAuthenticated} from 'src/auth'
import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor'
import DataExplorer from 'src/data_explorer'
@ -108,7 +107,6 @@ const Root = React.createClass({
<Route path="chronograf/data-explorer" component={DataExplorer} />
<Route path="hosts" component={HostsPage} />
<Route path="hosts/:hostID" component={HostPage} />
<Route path="kubernetes" component={KubernetesPage} />
<Route path="kapacitors/new" component={KapacitorPage} />
<Route path="kapacitors/:id/edit" component={KapacitorPage} />
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />

View File

@ -1,107 +0,0 @@
import React, {PropTypes} from 'react'
import classnames from 'classnames'
import LayoutRenderer from 'shared/components/LayoutRenderer'
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
import timeRanges from 'hson!../../shared/data/timeRanges.hson'
const {
arrayOf,
bool,
func,
number,
shape,
string,
} = PropTypes
export const KubernetesDashboard = React.createClass({
propTypes: {
source: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
telegraf: string.isRequired,
}),
layouts: arrayOf(shape().isRequired).isRequired,
autoRefresh: number.isRequired,
handleChooseAutoRefresh: func.isRequired,
inPresentationMode: bool.isRequired,
handleClickPresentationButton: func,
},
getInitialState() {
const fifteenMinutesIndex = 1
return {
timeRange: timeRanges[fifteenMinutesIndex],
}
},
renderLayouts(layouts) {
const {timeRange} = this.state
const {source, autoRefresh} = this.props
let layoutCells = []
layouts.forEach((layout) => {
layoutCells = layoutCells.concat(layout.cells)
})
layoutCells.forEach((cell, i) => {
cell.queries.forEach((q) => {
q.text = q.query
q.database = source.telegraf
})
cell.x = (i * 4 % 12) // eslint-disable-line no-magic-numbers
cell.y = 0
})
return (
<LayoutRenderer
timeRange={timeRange}
cells={layoutCells}
autoRefresh={autoRefresh}
source={source.links.proxy}
/>
)
},
handleChooseTimeRange({lower}) {
const timeRange = timeRanges.find((range) => range.lower === lower)
this.setState({timeRange})
},
render() {
const {layouts, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton, source} = this.props
const {timeRange} = this.state
const emptyState = (
<div className="generic-empty-state">
<span className="icon alert-triangle"></span>
<h4>No Kubernetes configuration found</h4>
</div>
)
return (
<div className="page">
<DashboardHeader
headerText="Kubernetes Dashboard"
autoRefresh={autoRefresh}
handleChooseAutoRefresh={handleChooseAutoRefresh}
timeRange={timeRange}
handleChooseTimeRange={this.handleChooseTimeRange}
isHidden={inPresentationMode}
handleClickPresentationButton={handleClickPresentationButton}
source={source}
/>
<div className={classnames({
'page-contents': true,
'presentation-mode': inPresentationMode,
})}>
<div className="container-fluid full-width dashboard">
{layouts.length ? this.renderLayouts(layouts) : emptyState}
</div>
</div>
</div>
)
},
})
export default KubernetesDashboard

View File

@ -1,72 +0,0 @@
import React, {PropTypes} from 'react'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {fetchLayouts} from 'shared/apis'
import KubernetesDashboard from 'src/kubernetes/components/KubernetesDashboard'
import {setAutoRefresh} from 'shared/actions/app'
import {presentationButtonDispatcher} from 'shared/dispatchers'
const {
bool,
func,
number,
shape,
string,
} = PropTypes
export const KubernetesPage = React.createClass({
propTypes: {
source: shape({
links: shape({
proxy: string.isRequired,
}).isRequired,
}),
autoRefresh: number.isRequired,
handleChooseAutoRefresh: func.isRequired,
inPresentationMode: bool.isRequired,
handleClickPresentationButton: func,
},
getInitialState() {
return {
layouts: [],
}
},
componentDidMount() {
fetchLayouts().then(({data: {layouts}}) => {
const kubernetesLayouts = layouts.filter((l) => l.app === 'kubernetes')
this.setState({layouts: kubernetesLayouts})
})
},
render() {
const {layouts} = this.state
const {source, autoRefresh, handleChooseAutoRefresh, inPresentationMode, handleClickPresentationButton} = this.props
return (
<KubernetesDashboard
layouts={layouts}
source={source}
autoRefresh={autoRefresh}
handleChooseAutoRefresh={handleChooseAutoRefresh}
inPresentationMode={inPresentationMode}
handleClickPresentationButton={handleClickPresentationButton}
/>
)
},
})
const mapStateToProps = ({app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}}}) => ({
inPresentationMode,
autoRefresh,
})
const mapDispatchToProps = (dispatch) => ({
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
handleClickPresentationButton: presentationButtonDispatcher(dispatch),
})
export default connect(mapStateToProps, mapDispatchToProps)(KubernetesPage)

View File

@ -1,2 +0,0 @@
import KubernetesPage from './containers/KubernetesPage'
export {KubernetesPage}

View File

@ -94,7 +94,7 @@ export const LayoutRenderer = React.createClass({
return cells.map((cell) => {
const qs = cell.queries.map((query) => {
// TODO: Canned dashboards (and possibly Kubernetes dashboard) use an old query schema,
// TODO: Canned dashboards use an old query schema,
// which does not have enough information for the new `buildInfluxQLQuery` function
// to operate on. We will use `buildQueryForOldQuerySchema` until we conform
// on a stable query representation.

View File

@ -9,7 +9,7 @@ const NoKapacitorError = React.createClass({
},
render() {
const path = `/sources/${this.props.source.id}/kapacitor-config`
const path = `/sources/${this.props.source.id}/kapacitors/new`
return (
<div>
<p>The current source does not have an associated Kapacitor instance, please configure one.</p>

View File

@ -3,11 +3,7 @@ import classNames from 'classnames'
import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText'
import _ from 'lodash'
const {
bool,
func,
shape,
} = PropTypes
const {bool, func, shape} = PropTypes
export const SourceForm = React.createClass({
propTypes: {
@ -28,7 +24,9 @@ export const SourceForm = React.createClass({
password: this.sourcePassword.value,
'default': this.sourceDefault.checked,
telegraf: this.sourceTelegraf.value,
insecureSkipVerify: this.sourceInsecureSkipVerify ? this.sourceInsecureSkipVerify.checked : false,
insecureSkipVerify: this.sourceInsecureSkipVerify
? this.sourceInsecureSkipVerify.checked
: false,
metaUrl: this.metaUrl && this.metaUrl.value.trim(),
}
@ -56,50 +54,126 @@ export const SourceForm = React.createClass({
return (
<div className="panel-body">
<h4 className="text-center">Connection Details</h4>
<br/>
<br />
<form onSubmit={this.handleSubmitForm}>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="connect-string"> Connection String</label>
<input type="text" name="url" ref={(r) => this.sourceURL = r} className="form-control" id="connect-string" placeholder="http://localhost:8086" onChange={onInputChange} value={source.url || ''} onBlur={this.handleBlurSourceURL}></input>
<input
type="text"
name="url"
ref={r => this.sourceURL = r}
className="form-control"
id="connect-string"
placeholder="http://localhost:8086"
onChange={onInputChange}
value={source.url || ''}
onBlur={this.handleBlurSourceURL}
required={true}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="name">Name</label>
<input type="text" name="name" ref={(r) => this.sourceName = r} className="form-control" id="name" placeholder="Influx 1" onChange={onInputChange} value={source.name || ''}></input>
<input
type="text"
name="name"
ref={r => this.sourceName = r}
className="form-control"
id="name"
placeholder="Influx 1"
onChange={onInputChange}
value={source.name || ''}
required={true}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="username">Username</label>
<input type="text" name="username" ref={(r) => this.sourceUsername = r} className="form-control" id="username" onChange={onInputChange} value={source.username || ''}></input>
<input
type="text"
name="username"
ref={r => this.sourceUsername = r}
className="form-control"
id="username"
onChange={onInputChange}
value={source.username || ''}
/>
</div>
<div className="form-group col-xs-12 col-sm-6">
<label htmlFor="password">Password</label>
<input type="password" name="password" ref={(r) => this.sourcePassword = r} className="form-control" id="password" onChange={onInputChange} value={source.password || ''}></input>
<input
type="password"
name="password"
ref={r => this.sourcePassword = r}
className="form-control"
id="password"
onChange={onInputChange}
value={source.password || ''}
/>
</div>
{_.get(source, 'type', '').includes('enterprise') ?
<div className="form-group col-xs-12">
{_.get(source, 'type', '').includes('enterprise')
? <div className="form-group col-xs-12">
<label htmlFor="meta-url">Meta Service Connection URL</label>
<input type="text" name="metaUrl" ref={(r) => this.metaUrl = r} className="form-control" id="meta-url" placeholder="http://localhost:8091" onChange={onInputChange} value={source.metaUrl || ''}></input>
</div> : null}
<input
type="text"
name="metaUrl"
ref={r => this.metaUrl = r}
className="form-control"
id="meta-url"
placeholder="http://localhost:8091"
onChange={onInputChange}
value={source.metaUrl || ''}
/>
</div>
: null}
<div className="form-group col-xs-12">
<label htmlFor="telegraf">Telegraf database</label>
<input type="text" name="telegraf" ref={(r) => this.sourceTelegraf = r} className="form-control" id="telegraf" onChange={onInputChange} value={source.telegraf || 'telegraf'}></input>
<input
type="text"
name="telegraf"
ref={r => this.sourceTelegraf = r}
className="form-control"
id="telegraf"
onChange={onInputChange}
value={source.telegraf || 'telegraf'}
/>
</div>
<div className="form-group col-xs-12">
<div className="form-control-static">
<input type="checkbox" id="defaultSourceCheckbox" defaultChecked={source.default} ref={(r) => this.sourceDefault = r} />
<label htmlFor="defaultSourceCheckbox">Make this the default source</label>
<input
type="checkbox"
id="defaultSourceCheckbox"
defaultChecked={source.default}
ref={r => this.sourceDefault = r}
/>
<label htmlFor="defaultSourceCheckbox">
Make this the default source
</label>
</div>
</div>
{_.get(source, 'url', '').startsWith('https') ?
<div className="form-group col-xs-12">
<div className="form-control-static">
<input type="checkbox" id="insecureSkipVerifyCheckbox" defaultChecked={source.insecureSkipVerify} ref={(r) => this.sourceInsecureSkipVerify = r} />
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
{_.get(source, 'url', '').startsWith('https')
? <div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="insecureSkipVerifyCheckbox"
defaultChecked={source.insecureSkipVerify}
ref={r => this.sourceInsecureSkipVerify = r}
/>
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div> : null}
: null}
<div className="form-group form-group-submit col-xs-12 col-sm-6 col-sm-offset-3">
<button className={classNames('btn btn-block', {'btn-primary': editMode, 'btn-success': !editMode})} type="submit">{editMode ? 'Save Changes' : 'Add Source'}</button>
<button
className={classNames('btn btn-block', {
'btn-primary': editMode,
'btn-success': !editMode,
})}
type="submit"
>
{editMode ? 'Save Changes' : 'Add Source'}
</button>
</div>
</form>
</div>