Merge branch 'master' into fix-gauge-resize
commit
e9bee9b30d
|
@ -1,10 +1,15 @@
|
|||
## v1.4.2.0 [unreleased]
|
||||
### Features
|
||||
1. [#2837] (https://github.com/influxdata/chronograf/pull/2837): Prevent execution of queries in cells that are not in view on the dashboard page
|
||||
### UI Improvements
|
||||
1. [#2848](https://github.com/influxdata/chronograf/pull/2848): Add ability to set a prefix and suffix on Single Stat and Gauge cell types
|
||||
1. [#2831](https://github.com/influxdata/chronograf/pull/2831): Rename 'Create Alerts' page to 'Manage Tasks'; Redesign page to improve clarity of purpose
|
||||
|
||||
### Bug Fixes
|
||||
1. [#2821](https://github.com/influxdata/chronograf/pull/2821): Save only selected template variable values into dashboards for non csv template variables
|
||||
1. [#2842](https://github.com/influxdata/chronograf/pull/2842): Use Generic APIKey for Oauth2 group lookup
|
||||
1. [#2850](https://github.com/influxdata/chronograf/pull/2850): Fix bug in which resizing any cell in a dashboard causes a Gauge cell to resize
|
||||
1. [#2851] (https://github.com/influxdata/chronograf/pull/2851): Maintain y axis labels in dashboard cells
|
||||
|
||||
## v1.4.1.3 [2018-02-14]
|
||||
### Bug Fixes
|
||||
|
|
|
@ -45,7 +45,7 @@ func newCellResponse(dID chronograf.DashboardID, cell chronograf.DashboardCell)
|
|||
for _, lbl := range []string{"x", "y", "y2"} {
|
||||
if _, found := newAxes[lbl]; !found {
|
||||
newAxes[lbl] = chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -354,6 +354,13 @@ func (s *Service) ReplaceDashboardCell(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
for i, a := range cell.Axes {
|
||||
if len(a.Bounds) == 0 {
|
||||
a.Bounds = []string{"", ""}
|
||||
cell.Axes[i] = a
|
||||
}
|
||||
}
|
||||
|
||||
if err := ValidDashboardCellRequest(&cell); err != nil {
|
||||
invalidData(w, err, s.Logger)
|
||||
return
|
||||
|
|
|
@ -192,13 +192,13 @@ func Test_Service_DashboardCells(t *testing.T) {
|
|||
CellColors: []chronograf.CellColor{},
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y2": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -420,13 +420,13 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
|
|||
},
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": {
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y": {
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y2": {
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
},
|
||||
Type: "line",
|
||||
|
@ -491,7 +491,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
|
|||
],
|
||||
"axes": {
|
||||
"x": {
|
||||
"bounds": [],
|
||||
"bounds": ["",""],
|
||||
"label": "",
|
||||
"prefix": "",
|
||||
"suffix": "",
|
||||
|
@ -499,7 +499,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
|
|||
"scale": ""
|
||||
},
|
||||
"y": {
|
||||
"bounds": [],
|
||||
"bounds": ["",""],
|
||||
"label": "",
|
||||
"prefix": "",
|
||||
"suffix": "",
|
||||
|
@ -507,7 +507,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
|
|||
"scale": ""
|
||||
},
|
||||
"y2": {
|
||||
"bounds": [],
|
||||
"bounds": ["",""],
|
||||
"label": "",
|
||||
"prefix": "",
|
||||
"suffix": "",
|
||||
|
@ -532,7 +532,7 @@ func TestService_ReplaceDashboardCell(t *testing.T) {
|
|||
}
|
||||
}
|
||||
`))),
|
||||
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":[],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":[],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":[],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
|
||||
want: `{"i":"3c5c4102-fa40-4585-a8f9-917c77e37192","x":0,"y":0,"w":4,"h":4,"name":"Untitled Cell","queries":[{"query":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","queryConfig":{"id":"3cd3eaa4-a4b8-44b3-b69e-0c7bf6b91d9e","database":"telegraf","measurement":"cpu","retentionPolicy":"autogen","fields":[{"value":"mean","type":"func","alias":"mean_usage_user","args":[{"value":"usage_user","type":"field","alias":""}]}],"tags":{"cpu":["ChristohersMBP2.lan"]},"groupBy":{"time":"2s","tags":[]},"areTagsAccepted":true,"fill":"null","rawText":"SELECT mean(\"usage_user\") AS \"mean_usage_user\" FROM \"telegraf\".\"autogen\".\"cpu\" WHERE time \u003e :dashboardTime: AND \"cpu\"=:cpu: GROUP BY :interval: FILL(null)","range":{"upper":"","lower":"now() - 15m"},"shifts":[]},"source":""}],"axes":{"x":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""},"y2":{"bounds":["",""],"label":"","prefix":"","suffix":"","base":"","scale":""}},"type":"line","colors":[{"id":"0","type":"min","hex":"#00C9FF","name":"laser","value":"0"},{"id":"1","type":"max","hex":"#9394FF","name":"comet","value":"100"}],"legend":{},"links":{"self":"/chronograf/v1/dashboards/1/cells/3c5c4102-fa40-4585-a8f9-917c77e37192"}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -854,13 +854,13 @@ func Test_newCellResponses(t *testing.T) {
|
|||
Queries: []chronograf.DashboardQuery{},
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y2": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
},
|
||||
CellColors: []chronograf.CellColor{},
|
||||
|
|
|
@ -299,7 +299,7 @@ func Test_newDashboardResponse(t *testing.T) {
|
|||
Label: "foo",
|
||||
},
|
||||
"y2": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -314,13 +314,13 @@ func Test_newDashboardResponse(t *testing.T) {
|
|||
H: 4,
|
||||
Axes: map[string]chronograf.Axis{
|
||||
"x": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
"y2": chronograf.Axis{
|
||||
Bounds: []string{},
|
||||
Bounds: []string{"", ""},
|
||||
},
|
||||
},
|
||||
CellColors: []chronograf.CellColor{},
|
||||
|
|
|
@ -124,6 +124,7 @@ class AxesOptions extends Component {
|
|||
value={prefix}
|
||||
labelText="Y-Value's Prefix"
|
||||
onChange={this.handleSetPrefixSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
<Input
|
||||
name="suffix"
|
||||
|
@ -131,6 +132,7 @@ class AxesOptions extends Component {
|
|||
value={suffix}
|
||||
labelText="Y-Value's Suffix"
|
||||
onChange={this.handleSetPrefixSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
<Tabber
|
||||
labelText="Y-Value's Format"
|
||||
|
|
|
@ -23,9 +23,14 @@ const Dashboard = ({
|
|||
onSummonOverlayTechnologies,
|
||||
onSelectTemplate,
|
||||
showTemplateControlBar,
|
||||
setScrollTop,
|
||||
inView,
|
||||
}) => {
|
||||
const cells = dashboard.cells.map(cell => {
|
||||
const dashboardCell = {...cell}
|
||||
const dashboardCell = {
|
||||
...cell,
|
||||
inView: inView(cell),
|
||||
}
|
||||
dashboardCell.queries = dashboardCell.queries.map(q => ({
|
||||
...q,
|
||||
database: q.db,
|
||||
|
@ -39,6 +44,7 @@ const Dashboard = ({
|
|||
className={classnames('page-contents', {
|
||||
'presentation-mode': inPresentationMode,
|
||||
})}
|
||||
setScrollTop={setScrollTop}
|
||||
>
|
||||
<div className="dashboard container-fluid full-width">
|
||||
{inPresentationMode
|
||||
|
@ -119,6 +125,8 @@ Dashboard.propTypes = {
|
|||
onSelectTemplate: func.isRequired,
|
||||
showTemplateControlBar: bool,
|
||||
onZoom: func,
|
||||
setScrollTop: func,
|
||||
inView: func,
|
||||
}
|
||||
|
||||
export default Dashboard
|
||||
|
|
|
@ -15,7 +15,10 @@ import {
|
|||
MIN_THRESHOLDS,
|
||||
} from 'src/dashboards/constants/gaugeColors'
|
||||
|
||||
import {updateGaugeColors} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
import {
|
||||
updateGaugeColors,
|
||||
updateAxes,
|
||||
} from 'src/dashboards/actions/cellEditorOverlay'
|
||||
|
||||
class GaugeOptions extends Component {
|
||||
handleAddThreshold = () => {
|
||||
|
@ -118,8 +121,22 @@ class GaugeOptions extends Component {
|
|||
return allowedToUpdate
|
||||
}
|
||||
|
||||
handleUpdatePrefix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, prefix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleUpdateSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, suffix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {gaugeColors} = this.props
|
||||
const {gaugeColors, axes: {y: {prefix, suffix}}} = this.props
|
||||
|
||||
const disableMaxColor = gaugeColors.length > MIN_THRESHOLDS
|
||||
const disableAddThreshold = gaugeColors.length > MAX_THRESHOLDS
|
||||
|
@ -157,6 +174,28 @@ class GaugeOptions extends Component {
|
|||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="single-stat-controls">
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Prefix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={prefix}
|
||||
onChange={this.handleUpdatePrefix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Suffix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={suffix}
|
||||
onChange={this.handleUpdateSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
)
|
||||
|
@ -176,13 +215,17 @@ GaugeOptions.propTypes = {
|
|||
}).isRequired
|
||||
),
|
||||
handleUpdateGaugeColors: func.isRequired,
|
||||
handleUpdateAxes: func.isRequired,
|
||||
axes: shape({}).isRequired,
|
||||
}
|
||||
|
||||
const mapStateToProps = ({cellEditorOverlay: {gaugeColors}}) => ({
|
||||
const mapStateToProps = ({cellEditorOverlay: {gaugeColors, cell: {axes}}}) => ({
|
||||
gaugeColors,
|
||||
axes,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleUpdateGaugeColors: bindActionCreators(updateGaugeColors, dispatch),
|
||||
handleUpdateAxes: bindActionCreators(updateAxes, dispatch),
|
||||
})
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(GaugeOptions)
|
||||
|
|
|
@ -109,6 +109,13 @@ class SingleStatOptions extends Component {
|
|||
return !sortedColors.some(color => color.value === targetValue)
|
||||
}
|
||||
|
||||
handleUpdatePrefix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, prefix: e.target.value}}
|
||||
|
||||
handleUpdateAxes(newAxes)
|
||||
}
|
||||
|
||||
handleUpdateSuffix = e => {
|
||||
const {handleUpdateAxes, axes} = this.props
|
||||
const newAxes = {...axes, y: {...axes.y, suffix: e.target.value}}
|
||||
|
@ -117,7 +124,11 @@ class SingleStatOptions extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {singleStatColors, singleStatType, axes: {y: {suffix}}} = this.props
|
||||
const {
|
||||
singleStatColors,
|
||||
singleStatType,
|
||||
axes: {y: {prefix, suffix}},
|
||||
} = this.props
|
||||
|
||||
const disableAddThreshold = singleStatColors.length > MAX_THRESHOLDS
|
||||
|
||||
|
@ -162,6 +173,26 @@ class SingleStatOptions extends Component {
|
|||
)}
|
||||
</div>
|
||||
<div className="single-stat-controls">
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Prefix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={prefix}
|
||||
onChange={this.handleUpdatePrefix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Suffix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={suffix}
|
||||
onChange={this.handleUpdateSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Coloring</label>
|
||||
<ul className="nav nav-tablist nav-tablist-sm">
|
||||
|
@ -183,16 +214,6 @@ class SingleStatOptions extends Component {
|
|||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className="form-group col-xs-6">
|
||||
<label>Suffix</label>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
placeholder="%, MPH, etc."
|
||||
defaultValue={suffix}
|
||||
onChange={this.handleUpdateSuffix}
|
||||
maxLength="5"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
|
|
|
@ -30,6 +30,7 @@ import {
|
|||
templateControlBarVisibilityToggled as templateControlBarVisibilityToggledAction,
|
||||
} from 'shared/actions/app'
|
||||
import {presentationButtonDispatcher} from 'shared/dispatchers'
|
||||
import {DASHBOARD_LAYOUT_ROW_HEIGHT} from 'shared/constants'
|
||||
|
||||
const FORMAT_INFLUXQL = 'influxql'
|
||||
const defaultTimeRange = {
|
||||
|
@ -47,6 +48,8 @@ class DashboardPage extends Component {
|
|||
selectedCell: null,
|
||||
isTemplating: false,
|
||||
zoomedTimeRange: {zoomedLower: null, zoomedUpper: null},
|
||||
scrollTop: 0,
|
||||
windowHeight: window.innerHeight,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -67,6 +70,8 @@ class DashboardPage extends Component {
|
|||
notify,
|
||||
} = this.props
|
||||
|
||||
window.addEventListener('resize', this.handleWindowResize, true)
|
||||
|
||||
const dashboards = await getDashboardsAsync()
|
||||
const dashboard = dashboards.find(
|
||||
d => d.id === idNormalizer(TYPE_ID, dashboardID)
|
||||
|
@ -86,6 +91,27 @@ class DashboardPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
handleWindowResize = () => {
|
||||
this.setState({windowHeight: window.innerHeight})
|
||||
}
|
||||
|
||||
componentWillUnMount() {
|
||||
window.removeEventListener('resize', this.handleWindowResize, true)
|
||||
}
|
||||
|
||||
inView = cell => {
|
||||
const {scrollTop, windowHeight} = this.state
|
||||
const bufferValue = 600
|
||||
const cellTop = cell.y * DASHBOARD_LAYOUT_ROW_HEIGHT
|
||||
const cellBottom = (cell.y + cell.h) * DASHBOARD_LAYOUT_ROW_HEIGHT
|
||||
const bufferedWindowBottom = windowHeight + scrollTop + bufferValue
|
||||
const bufferedWindowTop = scrollTop - bufferValue
|
||||
const topInView = cellTop < bufferedWindowBottom
|
||||
const bottomInView = cellBottom > bufferedWindowTop
|
||||
|
||||
return topInView && bottomInView
|
||||
}
|
||||
|
||||
handleOpenTemplateManager = () => {
|
||||
this.setState({isTemplating: true})
|
||||
}
|
||||
|
@ -228,6 +254,10 @@ class DashboardPage extends Component {
|
|||
this.setState({zoomedTimeRange: {zoomedLower, zoomedUpper}})
|
||||
}
|
||||
|
||||
setScrollTop = event => {
|
||||
this.setState({scrollTop: event.target.scrollTop})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {zoomedTimeRange} = this.state
|
||||
const {zoomedLower, zoomedUpper} = zoomedTimeRange
|
||||
|
@ -321,6 +351,7 @@ class DashboardPage extends Component {
|
|||
}
|
||||
|
||||
const {isEditMode, isTemplating} = this.state
|
||||
|
||||
const names = dashboards.map(d => ({
|
||||
name: d.name,
|
||||
link: `/sources/${sourceID}/dashboards/${d.id}`,
|
||||
|
@ -383,6 +414,8 @@ class DashboardPage extends Component {
|
|||
? <Dashboard
|
||||
source={source}
|
||||
sources={sources}
|
||||
setScrollTop={this.setScrollTop}
|
||||
inView={this.inView}
|
||||
dashboard={dashboard}
|
||||
timeRange={timeRange}
|
||||
autoRefresh={autoRefresh}
|
||||
|
|
|
@ -6,6 +6,7 @@ import SourceIndicator from 'shared/components/SourceIndicator'
|
|||
import KapacitorRulesTable from 'src/kapacitor/components/KapacitorRulesTable'
|
||||
import TasksTable from 'src/kapacitor/components/TasksTable'
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import QuestionMarkTooltip from 'shared/components/QuestionMarkTooltip'
|
||||
|
||||
const KapacitorRules = ({
|
||||
source,
|
||||
|
@ -41,17 +42,21 @@ const KapacitorRules = ({
|
|||
)
|
||||
}
|
||||
|
||||
const rulez = rules.filter(r => r.query)
|
||||
const tasks = rules.filter(r => !r.query)
|
||||
const builderRules = rules.filter(r => r.query)
|
||||
|
||||
const rHeader = `${rulez.length} Alert Rule${rulez.length === 1 ? '' : 's'}`
|
||||
const tHeader = `${tasks.length} TICKscript${tasks.length === 1 ? '' : 's'}`
|
||||
const builderHeader = `${builderRules.length} Alert Rule${builderRules.length ===
|
||||
1
|
||||
? ''
|
||||
: 's'}`
|
||||
const scriptsHeader = `${rules.length} TICKscript${rules.length === 1
|
||||
? ''
|
||||
: 's'}`
|
||||
|
||||
return (
|
||||
<PageContents source={source}>
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<h2 className="panel-title">
|
||||
{rHeader}
|
||||
{builderHeader}
|
||||
</h2>
|
||||
<div className="u-flex u-ai-center u-jc-space-between">
|
||||
<Link
|
||||
|
@ -65,7 +70,7 @@ const KapacitorRules = ({
|
|||
</div>
|
||||
<KapacitorRulesTable
|
||||
source={source}
|
||||
rules={rulez}
|
||||
rules={builderRules}
|
||||
onDelete={onDelete}
|
||||
onChangeRuleStatus={onChangeRuleStatus}
|
||||
/>
|
||||
|
@ -75,12 +80,12 @@ const KapacitorRules = ({
|
|||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<h2 className="panel-title">
|
||||
{tHeader}
|
||||
{scriptsHeader}
|
||||
</h2>
|
||||
<div className="u-flex u-ai-center u-jc-space-between">
|
||||
<Link
|
||||
to={`/sources/${source.id}/tickscript/new`}
|
||||
className="btn btn-sm btn-primary"
|
||||
className="btn btn-sm btn-success"
|
||||
style={{marginRight: '4px'}}
|
||||
>
|
||||
<span className="icon plus" /> Write TICKscript
|
||||
|
@ -89,7 +94,7 @@ const KapacitorRules = ({
|
|||
</div>
|
||||
<TasksTable
|
||||
source={source}
|
||||
tasks={tasks}
|
||||
tasks={rules}
|
||||
onDelete={onDelete}
|
||||
onChangeRuleStatus={onChangeRuleStatus}
|
||||
/>
|
||||
|
@ -105,11 +110,13 @@ const PageContents = ({children}) =>
|
|||
<div className="page-header">
|
||||
<div className="page-header__container">
|
||||
<div className="page-header__left">
|
||||
<h1 className="page-header__title">
|
||||
Build Alert Rules or Write TICKscripts
|
||||
</h1>
|
||||
<h1 className="page-header__title">Manage Tasks</h1>
|
||||
</div>
|
||||
<div className="page-header__right">
|
||||
<QuestionMarkTooltip
|
||||
tipID="manage-tasks--tooltip"
|
||||
tipContent="<b>Alert Rules</b> generate a TICKscript for<br/>you using our Builder UI.<br/><br/>Not all TICKscripts can be edited<br/>using the Builder."
|
||||
/>
|
||||
<SourceIndicator />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,8 +2,9 @@ import React, {PropTypes} from 'react'
|
|||
import {Link} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||
import {parseAlertNodeList} from 'src/shared/parsing/parseHandlersFromRule'
|
||||
import {KAPACITOR_RULES_TABLE} from 'src/kapacitor/constants/tableSizing'
|
||||
import {TASKS_TABLE} from 'src/kapacitor/constants/tableSizing'
|
||||
const {
|
||||
colName,
|
||||
colTrigger,
|
||||
|
@ -11,19 +12,19 @@ const {
|
|||
colAlerts,
|
||||
colEnabled,
|
||||
colActions,
|
||||
} = KAPACITOR_RULES_TABLE
|
||||
} = TASKS_TABLE
|
||||
|
||||
const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) =>
|
||||
<div className="panel-body">
|
||||
<table className="table v-center">
|
||||
<table className="table v-center table-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{width: colName}}>Name</th>
|
||||
<th style={{width: colTrigger}}>Rule Trigger</th>
|
||||
<th style={{minWidth: colName}}>Name</th>
|
||||
<th style={{width: colTrigger}}>Rule Type</th>
|
||||
<th style={{width: colMessage}}>Message</th>
|
||||
<th style={{width: colAlerts}}>Alerts</th>
|
||||
<th style={{width: colAlerts}}>Alert Handlers</th>
|
||||
<th style={{width: colEnabled}} className="text-center">
|
||||
Enabled
|
||||
Task Enabled
|
||||
</th>
|
||||
<th style={{width: colActions}} />
|
||||
</tr>
|
||||
|
@ -48,24 +49,21 @@ const handleDelete = (rule, onDelete) => onDelete(rule)
|
|||
|
||||
const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) =>
|
||||
<tr key={rule.id}>
|
||||
<td style={{width: colName}} className="monotype">
|
||||
<RuleTitle rule={rule} source={source} />
|
||||
<td style={{minWidth: colName}}>
|
||||
<Link to={`/sources/${source.id}/alert-rules/${rule.id}`}>
|
||||
{rule.name}
|
||||
</Link>
|
||||
</td>
|
||||
<td style={{width: colTrigger}} className="monotype">
|
||||
<td style={{width: colTrigger, textTransform: 'capitalize'}}>
|
||||
{rule.trigger}
|
||||
</td>
|
||||
<td className="monotype">
|
||||
<span
|
||||
className="table-cell-nowrap"
|
||||
style={{display: 'inline-block', maxWidth: colMessage}}
|
||||
>
|
||||
{rule.message}
|
||||
</span>
|
||||
<td style={{width: colMessage}}>
|
||||
{rule.message}
|
||||
</td>
|
||||
<td style={{width: colAlerts}} className="monotype">
|
||||
<td style={{width: colAlerts}}>
|
||||
{parseAlertNodeList(rule)}
|
||||
</td>
|
||||
<td style={{width: colEnabled}} className="monotype text-center">
|
||||
<td style={{width: colEnabled}} className="text-center">
|
||||
<div className="dark-checkbox">
|
||||
<input
|
||||
id={`kapacitor-enabled ${rule.id}`}
|
||||
|
@ -77,39 +75,17 @@ const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) =>
|
|||
<label htmlFor={`kapacitor-enabled ${rule.id}`} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
||||
<Link
|
||||
className="btn btn-info btn-xs"
|
||||
to={`/sources/${source.id}/tickscript/${rule.id}`}
|
||||
>
|
||||
Edit TICKscript
|
||||
</Link>
|
||||
<button
|
||||
className="btn btn-danger btn-xs"
|
||||
onClick={handleDelete(rule, onDelete)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<td style={{width: colActions}} className="text-right">
|
||||
<ConfirmButton
|
||||
text="Delete"
|
||||
type="btn-danger"
|
||||
size="btn-xs"
|
||||
customClass="table--show-on-row-hover"
|
||||
confirmAction={handleDelete(rule, onDelete)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
const RuleTitle = ({rule: {id, name, query}, source}) => {
|
||||
// no queryConfig means the rule was manually created outside of Chronograf
|
||||
if (!query) {
|
||||
return (
|
||||
<i>
|
||||
{name}
|
||||
</i>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Link to={`/sources/${source.id}/alert-rules/${id}`}>
|
||||
{name}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
|
||||
KapacitorRulesTable.propTypes = {
|
||||
|
@ -128,17 +104,4 @@ RuleRow.propTypes = {
|
|||
onDelete: func,
|
||||
}
|
||||
|
||||
RuleTitle.propTypes = {
|
||||
rule: shape({
|
||||
name: string.isRequired,
|
||||
query: shape(),
|
||||
links: shape({
|
||||
self: string.isRequired,
|
||||
}),
|
||||
}),
|
||||
source: shape({
|
||||
id: string.isRequired,
|
||||
}).isRequired,
|
||||
}
|
||||
|
||||
export default KapacitorRulesTable
|
||||
|
|
|
@ -2,25 +2,26 @@ import React, {PropTypes} from 'react'
|
|||
import {Link} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
import ConfirmButton from 'src/shared/components/ConfirmButton'
|
||||
import {TASKS_TABLE} from 'src/kapacitor/constants/tableSizing'
|
||||
|
||||
const {colID, colType, colEnabled, colActions} = TASKS_TABLE
|
||||
const {colName, colType, colEnabled, colActions} = TASKS_TABLE
|
||||
|
||||
const TasksTable = ({tasks, source, onDelete, onChangeRuleStatus}) =>
|
||||
<div className="panel-body">
|
||||
<table className="table v-center">
|
||||
<table className="table v-center table-highlight">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{width: colID}}>ID</th>
|
||||
<th style={{minWidth: colName}}>Name</th>
|
||||
<th style={{width: colType}}>Type</th>
|
||||
<th style={{width: colEnabled}} className="text-center">
|
||||
Enabled
|
||||
Task Enabled
|
||||
</th>
|
||||
<th style={{width: colActions}} />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{_.sortBy(tasks, t => t.name.toLowerCase()).map(task => {
|
||||
{_.sortBy(tasks, t => t.id.toLowerCase()).map(task => {
|
||||
return (
|
||||
<TaskRow
|
||||
key={task.id}
|
||||
|
@ -39,15 +40,18 @@ const handleDelete = (task, onDelete) => onDelete(task)
|
|||
|
||||
const TaskRow = ({task, source, onDelete, onChangeRuleStatus}) =>
|
||||
<tr key={task.id}>
|
||||
<td style={{width: colID}} className="monotype">
|
||||
<i>
|
||||
{task.id}
|
||||
</i>
|
||||
<td style={{minWidth: colName}}>
|
||||
<Link
|
||||
className="link-success"
|
||||
to={`/sources/${source.id}/tickscript/${task.id}`}
|
||||
>
|
||||
{task.name}
|
||||
</Link>
|
||||
</td>
|
||||
<td style={{width: colType}} className="monotype">
|
||||
<td style={{width: colType, textTransform: 'capitalize'}}>
|
||||
{task.type}
|
||||
</td>
|
||||
<td style={{width: colEnabled}} className="monotype text-center">
|
||||
<td style={{width: colEnabled}} className="text-center">
|
||||
<div className="dark-checkbox">
|
||||
<input
|
||||
id={`kapacitor-enabled ${task.id}`}
|
||||
|
@ -59,19 +63,14 @@ const TaskRow = ({task, source, onDelete, onChangeRuleStatus}) =>
|
|||
<label htmlFor={`kapacitor-enabled ${task.id}`} />
|
||||
</div>
|
||||
</td>
|
||||
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
||||
<Link
|
||||
className="btn btn-info btn-xs"
|
||||
to={`/sources/${source.id}/tickscript/${task.id}`}
|
||||
>
|
||||
Edit TICKscript
|
||||
</Link>
|
||||
<button
|
||||
className="btn btn-danger btn-xs"
|
||||
onClick={handleDelete(task, onDelete)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
<td style={{width: colActions}} className="text-right">
|
||||
<ConfirmButton
|
||||
text="Delete"
|
||||
type="btn-danger"
|
||||
size="btn-xs"
|
||||
customClass="table--show-on-row-hover"
|
||||
confirmAction={handleDelete(task, onDelete)}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
export const KAPACITOR_RULES_TABLE = {
|
||||
export const TASKS_TABLE = {
|
||||
colName: '200px',
|
||||
colTrigger: '90px',
|
||||
colMessage: '460px',
|
||||
colMessage: '200px',
|
||||
colAlerts: '120px',
|
||||
colEnabled: '64px',
|
||||
colActions: '176px',
|
||||
}
|
||||
|
||||
export const TASKS_TABLE = {
|
||||
colID: '200px',
|
||||
colEnabled: '95px',
|
||||
colActions: '68px',
|
||||
colType: '90px',
|
||||
colEnabled: '64px',
|
||||
colActions: '176px',
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
constructor() {
|
||||
super()
|
||||
this.state = {
|
||||
lastQuerySuccessful: false,
|
||||
lastQuerySuccessful: true,
|
||||
timeSeries: [],
|
||||
resolution: null,
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ const AutoRefresh = ComposedComponent => {
|
|||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
const inViewDidUpdate = this.props.inView !== nextProps.inView
|
||||
|
||||
const queriesDidUpdate = this.queryDifference(
|
||||
this.props.queries,
|
||||
nextProps.queries
|
||||
|
@ -37,10 +39,15 @@ const AutoRefresh = ComposedComponent => {
|
|||
nextProps.templates
|
||||
)
|
||||
|
||||
const shouldRefetch = queriesDidUpdate || tempVarsDidUpdate
|
||||
const shouldRefetch =
|
||||
queriesDidUpdate || tempVarsDidUpdate || inViewDidUpdate
|
||||
|
||||
if (shouldRefetch) {
|
||||
this.executeQueries(nextProps.queries, nextProps.templates)
|
||||
this.executeQueries(
|
||||
nextProps.queries,
|
||||
nextProps.templates,
|
||||
nextProps.inView
|
||||
)
|
||||
}
|
||||
|
||||
if (this.props.autoRefresh !== nextProps.autoRefresh || shouldRefetch) {
|
||||
|
@ -48,7 +55,12 @@ const AutoRefresh = ComposedComponent => {
|
|||
|
||||
if (nextProps.autoRefresh) {
|
||||
this.intervalID = setInterval(
|
||||
() => this.executeQueries(nextProps.queries, nextProps.templates),
|
||||
() =>
|
||||
this.executeQueries(
|
||||
nextProps.queries,
|
||||
nextProps.templates,
|
||||
nextProps.inView
|
||||
),
|
||||
nextProps.autoRefresh
|
||||
)
|
||||
}
|
||||
|
@ -64,10 +76,16 @@ const AutoRefresh = ComposedComponent => {
|
|||
)
|
||||
}
|
||||
|
||||
executeQueries = async (queries, templates = []) => {
|
||||
executeQueries = async (
|
||||
queries,
|
||||
templates = [],
|
||||
inView = this.props.inView
|
||||
) => {
|
||||
const {editQueryStatus, grabDataForDownload} = this.props
|
||||
const {resolution} = this.state
|
||||
|
||||
if (!inView) {
|
||||
return
|
||||
}
|
||||
if (!queries.length) {
|
||||
this.setState({timeSeries: []})
|
||||
return
|
||||
|
@ -148,7 +166,15 @@ const AutoRefresh = ComposedComponent => {
|
|||
const {timeSeries} = this.state
|
||||
|
||||
if (this.state.isFetching && this.state.lastQuerySuccessful) {
|
||||
return this.renderFetching(timeSeries)
|
||||
return (
|
||||
<ComposedComponent
|
||||
{...this.props}
|
||||
data={timeSeries}
|
||||
setResolution={this.setResolution}
|
||||
isFetchingInitially={false}
|
||||
isRefreshing={true}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -160,23 +186,6 @@ const AutoRefresh = ComposedComponent => {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Graphs can potentially show mulitple kinds of spinners based on whether
|
||||
* a graph is being fetched for the first time, or is being refreshed.
|
||||
*/
|
||||
renderFetching = data => {
|
||||
const isFirstFetch = !Object.keys(this.state.timeSeries).length
|
||||
return (
|
||||
<ComposedComponent
|
||||
{...this.props}
|
||||
data={data}
|
||||
setResolution={this.setResolution}
|
||||
isFetchingInitially={isFirstFetch}
|
||||
isRefreshing={!isFirstFetch}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
_resultsForQuery = data =>
|
||||
data.length
|
||||
? data.every(({response}) =>
|
||||
|
@ -204,6 +213,7 @@ const AutoRefresh = ComposedComponent => {
|
|||
wrapper.propTypes = {
|
||||
children: element,
|
||||
autoRefresh: number.isRequired,
|
||||
inView: bool,
|
||||
templates: arrayOf(
|
||||
shape({
|
||||
type: string.isRequired,
|
||||
|
|
|
@ -10,6 +10,7 @@ class FancyScrollbar extends Component {
|
|||
static defaultProps = {
|
||||
autoHide: true,
|
||||
autoHeight: false,
|
||||
setScrollTop: () => {},
|
||||
}
|
||||
|
||||
handleMakeDiv = className => props => {
|
||||
|
@ -17,13 +18,21 @@ class FancyScrollbar extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {autoHide, autoHeight, children, className, maxHeight} = this.props
|
||||
const {
|
||||
autoHide,
|
||||
autoHeight,
|
||||
children,
|
||||
className,
|
||||
maxHeight,
|
||||
setScrollTop,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<Scrollbars
|
||||
className={classnames('fancy-scroll--container', {
|
||||
[className]: className,
|
||||
})}
|
||||
onScroll={setScrollTop}
|
||||
autoHide={autoHide}
|
||||
autoHideTimeout={1000}
|
||||
autoHideDuration={250}
|
||||
|
@ -41,7 +50,7 @@ class FancyScrollbar extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {bool, node, number, string} = PropTypes
|
||||
const {bool, func, node, number, string} = PropTypes
|
||||
|
||||
FancyScrollbar.propTypes = {
|
||||
children: node.isRequired,
|
||||
|
@ -49,6 +58,7 @@ FancyScrollbar.propTypes = {
|
|||
autoHide: bool,
|
||||
autoHeight: bool,
|
||||
maxHeight: number,
|
||||
setScrollTop: func,
|
||||
}
|
||||
|
||||
export default FancyScrollbar
|
||||
|
|
|
@ -226,6 +226,7 @@ class Gauge extends Component {
|
|||
minValue,
|
||||
maxValue
|
||||
) => {
|
||||
const {prefix, suffix} = this.props
|
||||
const {degree, lineCount, labelColor, labelFontSize} = GAUGE_SPECS
|
||||
|
||||
const incrementValue = (maxValue - minValue) / lineCount
|
||||
|
@ -258,12 +259,14 @@ class Gauge extends Component {
|
|||
if (i > 3) {
|
||||
ctx.textAlign = 'left'
|
||||
}
|
||||
const labelText = `${prefix}${gaugeValues[i]}${suffix}`
|
||||
|
||||
ctx.rotate(startDegree)
|
||||
ctx.rotate(i * arcIncrement)
|
||||
ctx.translate(labelRadius, 0)
|
||||
ctx.rotate(i * -arcIncrement)
|
||||
ctx.rotate(-startDegree)
|
||||
ctx.fillText(gaugeValues[i], 0, 0)
|
||||
ctx.fillText(labelText, 0, 0)
|
||||
ctx.rotate(startDegree)
|
||||
ctx.rotate(i * arcIncrement)
|
||||
ctx.translate(-labelRadius, 0)
|
||||
|
@ -273,7 +276,7 @@ class Gauge extends Component {
|
|||
}
|
||||
|
||||
drawGaugeValue = (ctx, radius, labelValueFontSize) => {
|
||||
const {gaugePosition} = this.props
|
||||
const {gaugePosition, prefix, suffix} = this.props
|
||||
const {valueColor} = GAUGE_SPECS
|
||||
|
||||
ctx.font = `${labelValueFontSize}px Roboto`
|
||||
|
@ -282,7 +285,8 @@ class Gauge extends Component {
|
|||
ctx.textAlign = 'center'
|
||||
|
||||
const textY = radius
|
||||
ctx.fillText(gaugePosition.toString(), 0, textY)
|
||||
const textContent = `${prefix}${gaugePosition.toString()}${suffix}`
|
||||
ctx.fillText(textContent, 0, textY)
|
||||
}
|
||||
|
||||
drawNeedle = (ctx, radius, minValue, maxValue) => {
|
||||
|
@ -335,6 +339,8 @@ Gauge.propTypes = {
|
|||
value: string.isRequired,
|
||||
}).isRequired
|
||||
).isRequired,
|
||||
prefix: string.isRequired,
|
||||
suffix: string.isRequired,
|
||||
}
|
||||
|
||||
export default Gauge
|
||||
|
|
|
@ -18,6 +18,8 @@ class GaugeChart extends PureComponent {
|
|||
colors,
|
||||
resizeCoords,
|
||||
resizerTopHeight,
|
||||
prefix,
|
||||
suffix,
|
||||
} = this.props
|
||||
|
||||
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
|
||||
|
@ -61,6 +63,8 @@ class GaugeChart extends PureComponent {
|
|||
height={height}
|
||||
colors={colors}
|
||||
gaugePosition={roundedValue}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -89,6 +93,8 @@ GaugeChart.propTypes = {
|
|||
value: string.isRequired,
|
||||
}).isRequired
|
||||
),
|
||||
prefix: string.isRequired,
|
||||
suffix: string.isRequired,
|
||||
}
|
||||
|
||||
export default GaugeChart
|
||||
|
|
|
@ -76,6 +76,7 @@ const Layout = (
|
|||
? <WidgetCell cell={cell} timeRange={timeRange} source={source} />
|
||||
: <RefreshingGraph
|
||||
colors={colors}
|
||||
inView={cell.inView}
|
||||
axes={axes}
|
||||
type={type}
|
||||
cellHeight={h}
|
||||
|
|
|
@ -33,13 +33,11 @@ class LayoutRenderer extends Component {
|
|||
if (!this.props.onPositionChange) {
|
||||
return
|
||||
}
|
||||
|
||||
const newCells = this.props.cells.map(cell => {
|
||||
const l = layout.find(ly => ly.i === cell.i)
|
||||
const newLayout = {x: l.x, y: l.y, h: l.h, w: l.w}
|
||||
return {...cell, ...newLayout}
|
||||
})
|
||||
|
||||
this.props.onPositionChange(newCells)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ const RefreshingGaugeChart = AutoRefresh(GaugeChart)
|
|||
|
||||
const RefreshingGraph = ({
|
||||
axes,
|
||||
inView,
|
||||
type,
|
||||
colors,
|
||||
onZoom,
|
||||
|
@ -29,6 +30,9 @@ const RefreshingGraph = ({
|
|||
editQueryStatus,
|
||||
grabDataForDownload,
|
||||
}) => {
|
||||
const prefix = axes.y.prefix || ''
|
||||
const suffix = axes.y.suffix || ''
|
||||
|
||||
if (!queries.length) {
|
||||
return (
|
||||
<div className="graph-empty">
|
||||
|
@ -40,8 +44,6 @@ const RefreshingGraph = ({
|
|||
}
|
||||
|
||||
if (type === 'single-stat') {
|
||||
const suffix = axes.y.suffix || ''
|
||||
|
||||
return (
|
||||
<RefreshingSingleStat
|
||||
colors={colors}
|
||||
|
@ -50,6 +52,7 @@ const RefreshingGraph = ({
|
|||
templates={templates}
|
||||
autoRefresh={autoRefresh}
|
||||
cellHeight={cellHeight}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
/>
|
||||
)
|
||||
|
@ -67,6 +70,8 @@ const RefreshingGraph = ({
|
|||
resizerTopHeight={resizerTopHeight}
|
||||
resizeCoords={resizeCoords}
|
||||
cellID={cellID}
|
||||
prefix={prefix}
|
||||
suffix={suffix}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -82,6 +87,7 @@ const RefreshingGraph = ({
|
|||
colors={colors}
|
||||
onZoom={onZoom}
|
||||
queries={queries}
|
||||
inView={inView}
|
||||
key={manualRefresh}
|
||||
templates={templates}
|
||||
timeRange={timeRange}
|
||||
|
@ -97,7 +103,7 @@ const RefreshingGraph = ({
|
|||
)
|
||||
}
|
||||
|
||||
const {arrayOf, func, number, shape, string} = PropTypes
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
RefreshingGraph.propTypes = {
|
||||
timeRange: shape({
|
||||
|
@ -126,10 +132,12 @@ RefreshingGraph.propTypes = {
|
|||
}).isRequired
|
||||
),
|
||||
cellID: string,
|
||||
inView: bool,
|
||||
}
|
||||
|
||||
RefreshingGraph.defaultProps = {
|
||||
manualRefresh: 0,
|
||||
inView: true,
|
||||
}
|
||||
|
||||
export default RefreshingGraph
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import _ from 'lodash'
|
||||
|
||||
const emptyFunny = [
|
||||
'Looks like you don’t have any queries. Be a lot cooler if you did.',
|
||||
'Create a query. Go on, I dare ya!',
|
||||
'Looks like you don’t have any queries. Be a lot cooler if you did!',
|
||||
'Create a query. Go on!',
|
||||
'Create a query. Have fun!',
|
||||
]
|
||||
|
||||
|
|
|
@ -115,13 +115,15 @@ const SideNav = React.createClass({
|
|||
<NavBlock
|
||||
matcher="alerts"
|
||||
icon="alert-triangle"
|
||||
link={`${sourcePrefix}/alerts`}
|
||||
link={`${sourcePrefix}/alert-rules`}
|
||||
location={location}
|
||||
>
|
||||
<NavHeader link={`${sourcePrefix}/alerts`} title="Alerting" />
|
||||
<NavListItem link={`${sourcePrefix}/alerts`}>History</NavListItem>
|
||||
<NavHeader link={`${sourcePrefix}/alert-rules`} title="Alerting" />
|
||||
<NavListItem link={`${sourcePrefix}/alert-rules`}>
|
||||
Create
|
||||
Manage Tasks
|
||||
</NavListItem>
|
||||
<NavListItem link={`${sourcePrefix}/alerts`}>
|
||||
Alert History
|
||||
</NavListItem>
|
||||
</NavBlock>
|
||||
|
||||
|
|
|
@ -4778,7 +4778,8 @@ p .label {
|
|||
.table .table--show-on-row-hover {
|
||||
visibility: hidden;
|
||||
}
|
||||
.table > tbody > tr:hover .table--show-on-row-hover {
|
||||
.table > tbody > tr:hover .table--show-on-row-hover,
|
||||
.table > tbody > tr .active.table--show-on-row-hover {
|
||||
visibility: visible;
|
||||
}
|
||||
.table-responsive {
|
||||
|
|
Loading…
Reference in New Issue