Merge branch 'master' into bugfix/enable-disable
commit
7a07fa4e70
|
@ -1,10 +1,10 @@
|
||||||
## v1.3.4.0 [unreleased]
|
## v1.3.3.1 [2017-06-20]
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
1. [#1641](https://github.com/influxdata/chronograf/pull/1641): Fix enable / disable being out of sync on Kapacitor Rules Page
|
1. [#1641](https://github.com/influxdata/chronograf/pull/1641): Fix enable / disable being out of sync on Kapacitor Rules Page
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
### UI Improvements
|
### UI Improvements
|
||||||
|
1. [#1642](https://github.com/influxdata/chronograf/pull/1642): Do not prefix basepath to external link for news feed
|
||||||
|
|
||||||
## v1.3.3.0 [2017-06-19]
|
## v1.3.3.0 [2017-06-19]
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@
|
||||||
1. [#1512](https://github.com/influxdata/chronograf/pull/1512): Prevent legend from flowing over window bottom bound
|
1. [#1512](https://github.com/influxdata/chronograf/pull/1512): Prevent legend from flowing over window bottom bound
|
||||||
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Prevent Kapacitor configurations from having the same name
|
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Prevent Kapacitor configurations from having the same name
|
||||||
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Limit Kapacitor configuration names to 33 characters to fix display bug
|
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Limit Kapacitor configuration names to 33 characters to fix display bug
|
||||||
|
1. [#1622](https://github.com/influxdata/chronograf/pull/1622): Use function selector grid in Kapacitor rule builder query maker instead of dropdown
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
1. [#1512](https://github.com/influxdata/chronograf/pull/1512): Synchronize vertical crosshair at same time across all graphs in a dashboard
|
1. [#1512](https://github.com/influxdata/chronograf/pull/1512): Synchronize vertical crosshair at same time across all graphs in a dashboard
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
1. [#1599](https://github.com/influxdata/chronograf/pull/1599): Bar graph option added to dashboard
|
1. [#1599](https://github.com/influxdata/chronograf/pull/1599): Bar graph option added to dashboard
|
||||||
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Redesign source management table to be more intuitive
|
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Redesign source management table to be more intuitive
|
||||||
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Redesign Line + Single Stat cells to appear more like a sparkline, and improve legibility
|
1. [#1600](https://github.com/influxdata/chronograf/pull/1600): Redesign Line + Single Stat cells to appear more like a sparkline, and improve legibility
|
||||||
|
1. [#1639](https://github.com/influxdata/chronograf/pull/1639): Improve graph synchronization performance
|
||||||
|
|
||||||
## v1.3.2.1 [2017-06-06]
|
## v1.3.2.1 [2017-06-06]
|
||||||
|
|
||||||
|
|
|
@ -212,8 +212,14 @@ class DashboardPage extends Component {
|
||||||
|
|
||||||
synchronizer(dygraph) {
|
synchronizer(dygraph) {
|
||||||
const dygraphs = [...this.state.dygraphs, dygraph]
|
const dygraphs = [...this.state.dygraphs, dygraph]
|
||||||
if (dygraphs.length > 1) {
|
const {dashboards, params} = this.props
|
||||||
Dygraph.synchronize(dygraphs)
|
const dashboard = dashboards.find(d => d.id === +params.dashboardID)
|
||||||
|
if (dashboard && dygraphs.length === dashboard.cells.length) {
|
||||||
|
Dygraph.synchronize(dygraphs, {
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
range: false,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
this.setState({dygraphs})
|
this.setState({dygraphs})
|
||||||
}
|
}
|
||||||
|
@ -266,8 +272,10 @@ class DashboardPage extends Component {
|
||||||
values: [],
|
values: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const templatesIncludingDashTime = (dashboard &&
|
const templatesIncludingDashTime =
|
||||||
dashboard.templates.concat(dashboardTime).concat(interval)) || []
|
(dashboard &&
|
||||||
|
dashboard.templates.concat(dashboardTime).concat(interval)) ||
|
||||||
|
[]
|
||||||
|
|
||||||
const {selectedCell, isEditMode, isTemplating} = this.state
|
const {selectedCell, isEditMode, isTemplating} = this.state
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,6 @@ import classnames from 'classnames'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
|
|
||||||
import FunctionSelector from 'shared/components/FunctionSelector'
|
import FunctionSelector from 'shared/components/FunctionSelector'
|
||||||
import Dropdown from 'shared/components/Dropdown'
|
|
||||||
|
|
||||||
import {INFLUXQL_FUNCTIONS} from '../constants'
|
|
||||||
|
|
||||||
const {string, shape, func, arrayOf, bool} = PropTypes
|
const {string, shape, func, arrayOf, bool} = PropTypes
|
||||||
const FieldListItem = React.createClass({
|
const FieldListItem = React.createClass({
|
||||||
|
@ -41,7 +38,7 @@ const FieldListItem = React.createClass({
|
||||||
handleApplyFunctions(selectedFuncs) {
|
handleApplyFunctions(selectedFuncs) {
|
||||||
this.props.onApplyFuncsToField({
|
this.props.onApplyFuncsToField({
|
||||||
field: this.props.fieldFunc.field,
|
field: this.props.fieldFunc.field,
|
||||||
funcs: this.props.isKapacitorRule ? [selectedFuncs.text] : selectedFuncs,
|
funcs: selectedFuncs,
|
||||||
})
|
})
|
||||||
this.setState({isOpen: false})
|
this.setState({isOpen: false})
|
||||||
},
|
},
|
||||||
|
@ -50,38 +47,6 @@ const FieldListItem = React.createClass({
|
||||||
const {isKapacitorRule, fieldFunc, isSelected} = this.props
|
const {isKapacitorRule, fieldFunc, isSelected} = this.props
|
||||||
const {isOpen} = this.state
|
const {isOpen} = this.state
|
||||||
const {field: fieldText} = fieldFunc
|
const {field: fieldText} = fieldFunc
|
||||||
const items = INFLUXQL_FUNCTIONS.map(text => {
|
|
||||||
return {text}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (isKapacitorRule) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classnames('query-builder--list-item', {
|
|
||||||
active: isSelected,
|
|
||||||
})}
|
|
||||||
key={fieldFunc}
|
|
||||||
onClick={_.wrap(fieldFunc, this.handleToggleField)}
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
<div className="query-builder--checkbox" />
|
|
||||||
{fieldText}
|
|
||||||
</span>
|
|
||||||
{isSelected
|
|
||||||
? <Dropdown
|
|
||||||
className="dropdown-110"
|
|
||||||
menuClass="dropdown-malachite"
|
|
||||||
buttonSize="btn-xs"
|
|
||||||
items={items}
|
|
||||||
onChoose={this.handleApplyFunctions}
|
|
||||||
selected={
|
|
||||||
fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let fieldFuncsLabel
|
let fieldFuncsLabel
|
||||||
if (!fieldFunc.funcs.length) {
|
if (!fieldFunc.funcs.length) {
|
||||||
|
@ -121,6 +86,7 @@ const FieldListItem = React.createClass({
|
||||||
? <FunctionSelector
|
? <FunctionSelector
|
||||||
onApply={this.handleApplyFunctions}
|
onApply={this.handleApplyFunctions}
|
||||||
selectedItems={fieldFunc.funcs || []}
|
selectedItems={fieldFunc.funcs || []}
|
||||||
|
singleSelect={isKapacitorRule}
|
||||||
/>
|
/>
|
||||||
: null}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,6 +5,8 @@ import {bindActionCreators} from 'redux'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
import Dygraph from 'src/external/dygraph'
|
||||||
|
|
||||||
import LayoutRenderer from 'shared/components/LayoutRenderer'
|
import LayoutRenderer from 'shared/components/LayoutRenderer'
|
||||||
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
import DashboardHeader from 'src/dashboards/components/DashboardHeader'
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||||
|
@ -51,6 +53,7 @@ export const HostPage = React.createClass({
|
||||||
layouts: [],
|
layouts: [],
|
||||||
hosts: [],
|
hosts: [],
|
||||||
timeRange: timeRanges.find(tr => tr.lower === 'now() - 1h'),
|
timeRange: timeRanges.find(tr => tr.lower === 'now() - 1h'),
|
||||||
|
dygraphs: [],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -100,6 +103,22 @@ export const HostPage = React.createClass({
|
||||||
this.setState({timeRange})
|
this.setState({timeRange})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
synchronizer(dygraph) {
|
||||||
|
const dygraphs = [...this.state.dygraphs, dygraph]
|
||||||
|
const numGraphs = this.state.layouts.reduce((acc, {cells}) => {
|
||||||
|
return acc + cells.length
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
if (dygraphs.length === numGraphs) {
|
||||||
|
Dygraph.synchronize(dygraphs, {
|
||||||
|
selection: true,
|
||||||
|
zoom: false,
|
||||||
|
range: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.setState({dygraphs})
|
||||||
|
},
|
||||||
|
|
||||||
renderLayouts(layouts) {
|
renderLayouts(layouts) {
|
||||||
const {timeRange} = this.state
|
const {timeRange} = this.state
|
||||||
const {source, autoRefresh} = this.props
|
const {source, autoRefresh} = this.props
|
||||||
|
@ -156,6 +175,7 @@ export const HostPage = React.createClass({
|
||||||
source={source}
|
source={source}
|
||||||
host={this.props.params.hostID}
|
host={this.props.params.hostID}
|
||||||
shouldNotBeEditable={true}
|
shouldNotBeEditable={true}
|
||||||
|
synchronizer={this.synchronizer}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,6 +12,7 @@ class FunctionSelector extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onSelect = ::this.onSelect
|
this.onSelect = ::this.onSelect
|
||||||
|
this.onSingleSelect = ::this.onSingleSelect
|
||||||
this.handleApplyFunctions = ::this.handleApplyFunctions
|
this.handleApplyFunctions = ::this.handleApplyFunctions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +37,16 @@ class FunctionSelector extends Component {
|
||||||
this.setState({localSelectedItems: nextItems})
|
this.setState({localSelectedItems: nextItems})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSingleSelect(item) {
|
||||||
|
if (item === this.state.localSelectedItems[0]) {
|
||||||
|
this.props.onApply([])
|
||||||
|
this.setState({localSelectedItems: []})
|
||||||
|
} else {
|
||||||
|
this.props.onApply([item])
|
||||||
|
this.setState({localSelectedItems: [item]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
isSelected(item) {
|
isSelected(item) {
|
||||||
return !!this.state.localSelectedItems.find(text => text === item)
|
return !!this.state.localSelectedItems.find(text => text === item)
|
||||||
}
|
}
|
||||||
|
@ -48,10 +59,13 @@ class FunctionSelector extends Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {localSelectedItems} = this.state
|
const {localSelectedItems} = this.state
|
||||||
|
const {singleSelect} = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="function-selector">
|
<div className="function-selector">
|
||||||
<div className="function-selector--header">
|
{singleSelect
|
||||||
|
? null
|
||||||
|
: <div className="function-selector--header">
|
||||||
<span>
|
<span>
|
||||||
{localSelectedItems.length > 0
|
{localSelectedItems.length > 0
|
||||||
? `${localSelectedItems.length} Selected`
|
? `${localSelectedItems.length} Selected`
|
||||||
|
@ -63,7 +77,7 @@ class FunctionSelector extends Component {
|
||||||
>
|
>
|
||||||
Apply
|
Apply
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>}
|
||||||
<div className="function-selector--grid">
|
<div className="function-selector--grid">
|
||||||
{INFLUXQL_FUNCTIONS.map((f, i) => {
|
{INFLUXQL_FUNCTIONS.map((f, i) => {
|
||||||
return (
|
return (
|
||||||
|
@ -72,7 +86,10 @@ class FunctionSelector extends Component {
|
||||||
className={classnames('function-selector--item', {
|
className={classnames('function-selector--item', {
|
||||||
active: this.isSelected(f),
|
active: this.isSelected(f),
|
||||||
})}
|
})}
|
||||||
onClick={_.wrap(f, this.onSelect)}
|
onClick={_.wrap(
|
||||||
|
f,
|
||||||
|
singleSelect ? this.onSingleSelect : this.onSelect
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
{f}
|
{f}
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,11 +101,12 @@ class FunctionSelector extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {arrayOf, func, string} = PropTypes
|
const {arrayOf, bool, func, string} = PropTypes
|
||||||
|
|
||||||
FunctionSelector.propTypes = {
|
FunctionSelector.propTypes = {
|
||||||
onApply: func.isRequired,
|
onApply: func.isRequired,
|
||||||
selectedItems: arrayOf(string.isRequired).isRequired,
|
selectedItems: arrayOf(string.isRequired).isRequired,
|
||||||
|
singleSelect: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FunctionSelector
|
export default FunctionSelector
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import AJAX from 'utils/ajax'
|
import AJAX from 'utils/ajax'
|
||||||
|
|
||||||
export const fetchJSONFeed = url =>
|
export const fetchJSONFeed = url =>
|
||||||
AJAX({
|
AJAX(
|
||||||
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url,
|
url,
|
||||||
// For explanation of why this header makes this work:
|
// For explanation of why this header makes this work:
|
||||||
// https://stackoverflow.com/questions/22968406/how-to-skip-the-options-preflight-request-in-angularjs
|
// https://stackoverflow.com/questions/22968406/how-to-skip-the-options-preflight-request-in-angularjs
|
||||||
headers: {'Content-Type': 'text/plain; charset=UTF-8'},
|
headers: {'Content-Type': 'text/plain; charset=UTF-8'},
|
||||||
})
|
},
|
||||||
|
true // don't prefix route of external link with basepath
|
||||||
|
)
|
||||||
|
|
|
@ -33,10 +33,12 @@ $function-selector--text-active: $g20-white;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: $function-selector--gutter;
|
padding: $function-selector--gutter;
|
||||||
padding-top: 0;
|
|
||||||
border-radius: 0 0 $radius $radius;
|
border-radius: 0 0 $radius $radius;
|
||||||
background-color: $function-selector--bg;
|
background-color: $function-selector--bg;
|
||||||
}
|
}
|
||||||
|
.function-selector--header + .function-selector--grid {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
.function-selector--item {
|
.function-selector--item {
|
||||||
@include no-user-select();
|
@include no-user-select();
|
||||||
border-radius: $radius;
|
border-radius: $radius;
|
||||||
|
|
|
@ -2,6 +2,13 @@ import axios from 'axios'
|
||||||
|
|
||||||
let links
|
let links
|
||||||
|
|
||||||
|
// do not prefix route with basepath, ex. for external links
|
||||||
|
const addBasepath = (url, excludeBasepath) => {
|
||||||
|
const basepath = window.basepath || ''
|
||||||
|
|
||||||
|
return excludeBasepath ? url : `${basepath}${url}`
|
||||||
|
}
|
||||||
|
|
||||||
const generateResponseWithLinks = (response, {auth, logout, external}) => ({
|
const generateResponseWithLinks = (response, {auth, logout, external}) => ({
|
||||||
...response,
|
...response,
|
||||||
auth: {links: auth},
|
auth: {links: auth},
|
||||||
|
@ -9,24 +16,18 @@ const generateResponseWithLinks = (response, {auth, logout, external}) => ({
|
||||||
external,
|
external,
|
||||||
})
|
})
|
||||||
|
|
||||||
const AJAX = async ({
|
const AJAX = async (
|
||||||
url,
|
{url, resource, id, method = 'GET', data = {}, params = {}, headers = {}},
|
||||||
resource,
|
excludeBasepath
|
||||||
id,
|
) => {
|
||||||
method = 'GET',
|
|
||||||
data = {},
|
|
||||||
params = {},
|
|
||||||
headers = {},
|
|
||||||
}) => {
|
|
||||||
try {
|
try {
|
||||||
const basepath = window.basepath || ''
|
|
||||||
let response
|
let response
|
||||||
|
|
||||||
url = `${basepath}${url}`
|
url = addBasepath(url, excludeBasepath)
|
||||||
|
|
||||||
if (!links) {
|
if (!links) {
|
||||||
const linksRes = (response = await axios({
|
const linksRes = (response = await axios({
|
||||||
url: `${basepath}/chronograf/v1`,
|
url: addBasepath('/chronograf/v1', excludeBasepath),
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
}))
|
}))
|
||||||
links = linksRes.data
|
links = linksRes.data
|
||||||
|
@ -34,8 +35,8 @@ const AJAX = async ({
|
||||||
|
|
||||||
if (resource) {
|
if (resource) {
|
||||||
url = id
|
url = id
|
||||||
? `${basepath}${links[resource]}/${id}`
|
? addBasepath(`${links[resource]}/${id}`, excludeBasepath)
|
||||||
: `${basepath}${links[resource]}`
|
: addBasepath(`${links[resource]}`, excludeBasepath)
|
||||||
}
|
}
|
||||||
|
|
||||||
response = await axios({
|
response = await axios({
|
||||||
|
|
Loading…
Reference in New Issue