Merge branch 'master' into bugfix/basepath-logout

pull/10616/head
Jared Scheib 2018-04-20 19:03:49 -07:00 committed by GitHub
commit 625b2f8c3f
16 changed files with 288 additions and 189 deletions

View File

@ -14,6 +14,8 @@
### Bug Fixes
1. [#3252](https://github.com/influxdata/chronograf/pull/3252): Allows users to select tickscript editor with mouse
1. [#3279](https://github.com/influxdata/chronograf/pull/3279): Change color when value is equal to or greater than threshold value
1. [#3281](https://github.com/influxdata/chronograf/pull/3281): Fix base path for kapacitor logs
1. [#3284](https://github.com/influxdata/chronograf/pull/3284): Fix logout when using basepath & simplify basepath usage (deprecates `PREFIX_ROUTES`)
## v1.4.4.1 [2018-04-16]

View File

@ -1,9 +1,11 @@
import React, {ReactElement, Component} from 'react'
import _ from 'lodash'
import PropTypes from 'prop-types'
import {withRouter, InjectedRouter} from 'react-router'
import {Location} from 'history'
import {connect} from 'react-redux'
import {bindActionCreators} from 'redux'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {
isUserAuthorized,
@ -52,6 +54,7 @@ interface Props {
// Acts as a 'router middleware'. The main `App` component is responsible for
// getting the list of data sources, but not every page requires them to function.
// Routes that do require data sources can be nested under this component.
@ErrorHandling
export class CheckSources extends Component<Props, State> {
public static childContextTypes = {
source: PropTypes.shape({
@ -111,9 +114,11 @@ export class CheckSources extends Component<Props, State> {
params,
errorThrown,
sources,
auth: {isUsingAuth, me, me: {organizations = [], currentOrganization}},
auth: {isUsingAuth, me},
notify,
} = nextProps
const organizations = _.get(me, 'organizations', [])
const currentOrganization = _.get(me, 'currentOrganization')
const {isFetching} = nextState
const source = sources.find(s => s.id === params.sourceID)
const defaultSource = sources.find(s => s.default === true)

View File

@ -299,7 +299,7 @@ export const addDashboardCellAsync = (
getNewDashboardCell(dashboard, cellType)
)
dispatch(addDashboardCell(dashboard, data))
dispatch(notify(notifyCellAdded()))
dispatch(notify(notifyCellAdded(data.name)))
} catch (error) {
console.error(error)
dispatch(errorThrown(error))

View File

@ -1,4 +1,4 @@
import React, {PureComponent, ChangeEvent} from 'react'
import React, {Component, ChangeEvent} from 'react'
import {findDOMNode} from 'react-dom'
import {
DragSourceSpec,
@ -112,7 +112,7 @@ function MyDragSource(dragv1, dragv2, dragfunc1) {
isDragging: monitor.isDragging(),
})
)
export default class GraphOptionsCustomizableField extends PureComponent<
export default class GraphOptionsCustomizableField extends Component<
GraphOptionsCustomizableFieldProps
> {
constructor(props) {

View File

@ -1,5 +1,26 @@
import {DEFAULT_TABLE_OPTIONS} from 'src/shared/constants/tableGraph'
import {CELL_TYPE_LINE, UNTITLED_CELL_LINE} from 'src/dashboards/graphics/graph'
import {CELL_TYPE_LINE} from 'src/dashboards/graphics/graph'
export const UNTITLED_CELL_LINE = 'Untitled Line Graph'
export const UNTITLED_CELL_STACKED = 'Untitled Stacked Gracph'
export const UNTITLED_CELL_STEPPLOT = 'Untitled Step-Plot Graph'
export const UNTITLED_CELL_BAR = 'Untitled Bar Graph'
export const UNTITLED_CELL_LINE_PLUS_SINGLE_STAT =
'Untitled Line Graph + Single Stat'
export const UNTITLED_CELL_SINGLE_STAT = 'Untitled Single Stat'
export const UNTITLED_CELL_GAUGE = 'Untitled Gauge'
export const UNTITLED_CELL_TABLE = 'Untitled Table'
export const NEW_DEFAULT_DASHBOARD_CELL = {
x: 0,
y: 0,
w: 4,
h: 4,
name: UNTITLED_CELL_LINE,
type: CELL_TYPE_LINE,
queries: [],
tableOptions: DEFAULT_TABLE_OPTIONS,
}
export const EMPTY_DASHBOARD = {
id: 0,
@ -15,17 +36,6 @@ export const EMPTY_DASHBOARD = {
],
}
export const NEW_DEFAULT_DASHBOARD_CELL = {
x: 0,
y: 0,
w: 4,
h: 4,
name: UNTITLED_CELL_LINE,
type: CELL_TYPE_LINE,
queries: [],
tableOptions: DEFAULT_TABLE_OPTIONS,
}
export const NEW_DASHBOARD = {
name: 'Name This Dashboard',
cells: [NEW_DEFAULT_DASHBOARD_CELL],

View File

@ -1,16 +1,21 @@
import _ from 'lodash'
import calculateSize from 'calculate-size'
export const minDropdownWidth = 146
export const maxDropdownWidth = 300
export const minDropdownWidth = 120
export const maxDropdownWidth = 330
export const dropdownPadding = 30
const valueLength = a => _.size(a.value)
export const calculateDropdownWidth = (values = []) => {
const longestValue = _.maxBy(values, valueLength)
export const calculateDropdownWidth = values => {
if (!values || !values.length) {
return minDropdownWidth
}
const longest = _.maxBy(values, valueLength)
const longestValuePixels =
calculateSize(longestValue, {
calculateSize(longest.value, {
font: 'Monospace',
fontSize: '12px',
}).width + dropdownPadding

View File

@ -19,7 +19,7 @@ class DashboardsPage extends Component {
this.props.handleGetDashboards()
}
handleCreateDashbord = async () => {
handleCreateDashboard = async () => {
const {source: {id}, router: {push}} = this.props
const {data} = await createDashboard(NEW_DASHBOARD)
push(`/sources/${id}/dashboards/${data.id}`)
@ -49,7 +49,7 @@ class DashboardsPage extends Component {
dashboardLink={dashboardLink}
dashboards={dashboards}
onDeleteDashboard={this.handleDeleteDashboard}
onCreateDashboard={this.handleCreateDashbord}
onCreateDashboard={this.handleCreateDashboard}
onCloneDashboard={this.handleCloneDashboard}
/>
</div>

View File

@ -9,6 +9,16 @@ import {
CELL_TYPE_GAUGE,
CELL_TYPE_TABLE,
} from 'src/dashboards/graphics/graph'
import {
UNTITLED_CELL_LINE,
UNTITLED_CELL_STACKED,
UNTITLED_CELL_STEPPLOT,
UNTITLED_CELL_BAR,
UNTITLED_CELL_LINE_PLUS_SINGLE_STAT,
UNTITLED_CELL_SINGLE_STAT,
UNTITLED_CELL_GAUGE,
UNTITLED_CELL_TABLE,
} from 'src/dashboards/constants'
const getMostCommonValue = values => {
const results = values.reduce(
@ -30,16 +40,6 @@ const getMostCommonValue = values => {
return results.mostCommonValue
}
export const UNTITLED_CELL_LINE = 'Untitled Line Graph'
export const UNTITLED_CELL_STACKED = 'Untitled Stacked Graph'
export const UNTITLED_CELL_STEPPLOT = 'Untitled Step-Plot Graph'
export const UNTITLED_CELL_BAR = 'Untitled Bar Graph'
export const UNTITLED_CELL_LINE_PLUS_SINGLE_STAT =
'Untitled Line Graph + Single Stat'
export const UNTITLED_CELL_SINGLE_STAT = 'Untitled Single Stat'
export const UNTITLED_CELL_GAUGE = 'Untitled Gauge'
export const UNTITLED_CELL_TABLE = 'Untitled Table'
const getNewTypedCellName = type => {
switch (type) {
case CELL_TYPE_LINE:

View File

@ -107,21 +107,22 @@ const kapacitorLogHeaders = {
}
export const getLogStream = kapacitor =>
fetch(`${kapacitor.links.proxy}?path=/kapacitor/v1preview/logs`, {
AJAX({
url: `${kapacitor.links.proxy}?path=/kapacitor/v1preview/logs`,
method: 'GET',
headers: kapacitorLogHeaders,
credentials: 'include',
})
export const getLogStreamByRuleID = (kapacitor, ruleID) =>
fetch(
`${kapacitor.links.proxy}?path=/kapacitor/v1preview/logs?task=${ruleID}`,
{
method: 'GET',
headers: kapacitorLogHeaders,
credentials: 'include',
}
)
AJAX({
url: `${
kapacitor.links.proxy
}?path=/kapacitor/v1preview/logs?task=${ruleID}`,
method: 'GET',
headers: kapacitorLogHeaders,
credentials: 'include',
})
export const pingKapacitorVersion = async kapacitor => {
try {

View File

@ -1,11 +1,11 @@
import React, {ChangeEvent, MouseEvent, SFC} from 'react'
import React, {ChangeEvent, MouseEvent, PureComponent} from 'react'
import AlertOutputs from 'src/kapacitor/components/AlertOutputs'
import Input from 'src/kapacitor/components/KapacitorFormInput'
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText'
import {Kapacitor, Source} from 'src/types'
import KapacitorFormSkipVerify from 'src/kapacitor/components/KapacitorFormSkipVerify'
export interface Notification {
id?: string
@ -30,125 +30,149 @@ interface Props {
notify: (message: Notification | NotificationFunc) => void
}
const KapacitorForm: SFC<Props> = ({
onChangeUrl,
onReset,
kapacitor,
kapacitor: {url, name, username, password, insecureSkipVerify},
onSubmit,
exists,
onInputChange,
onCheckboxChange,
source,
hash,
notify,
}) => (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">{`${
exists ? 'Configure' : 'Add a New'
} Kapacitor Connection`}</h1>
class KapacitorForm extends PureComponent<Props> {
public render() {
const {
onChangeUrl,
onReset,
kapacitor,
kapacitor: {name, username, password},
onSubmit,
exists,
onInputChange,
onCheckboxChange,
source,
hash,
notify,
} = this.props
return (
<div className="page">
<div className="page-header">
<div className="page-header__container">
<div className="page-header__left">
<h1 className="page-header__title">{this.headerText}</h1>
</div>
</div>
</div>
</div>
</div>
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-3">
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">Connection Details</h2>
</div>
<div className="panel-body">
<form onSubmit={onSubmit}>
<div>
<Input
name="kapaUrl"
label="Kapacitor URL"
value={url}
placeholder={url}
onChange={onChangeUrl}
/>
<Input
name="name"
label="Name"
value={name}
placeholder={name}
onChange={onInputChange}
maxLength={33}
/>
<Input
name="username"
label="Username"
value={username || ''}
placeholder={username}
onChange={onInputChange}
/>
<Input
name="password"
label="password"
placeholder="password"
value={password || ''}
onChange={onInputChange}
inputType="password"
/>
<FancyScrollbar className="page-contents">
<div className="container-fluid">
<div className="row">
<div className="col-md-3">
<div className="panel">
<div className="panel-heading">
<h2 className="panel-title">Connection Details</h2>
</div>
{url &&
url.startsWith('https') && (
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="insecureSkipVerifyCheckbox"
name="insecureSkipVerify"
checked={insecureSkipVerify}
onChange={onCheckboxChange}
/>
<label htmlFor="insecureSkipVerifyCheckbox">
Unsafe SSL
</label>
</div>
<label className="form-helper">
{insecureSkipVerifyText}
</label>
<div className="panel-body">
<form onSubmit={onSubmit}>
<div>
<Input
name="kapaUrl"
label="Kapacitor URL"
value={this.url}
placeholder={this.url}
onChange={onChangeUrl}
/>
<Input
name="name"
label="Name"
value={name}
placeholder={name}
onChange={onInputChange}
maxLength={33}
/>
<Input
name="username"
label="Username"
value={username || ''}
placeholder={username}
onChange={onInputChange}
/>
<Input
name="password"
label="password"
placeholder="password"
value={password || ''}
onChange={onInputChange}
inputType="password"
/>
</div>
)}
<div className="form-group form-group-submit col-xs-12 text-center">
<button
className="btn btn-default"
type="button"
onClick={onReset}
data-test="reset-button"
>
Reset
</button>
<button
className="btn btn-success"
type="submit"
data-test="submit-button"
>
{exists ? 'Update' : 'Connect'}
</button>
{this.isSecure && (
<KapacitorFormSkipVerify
kapacitor={kapacitor}
onCheckboxChange={onCheckboxChange}
/>
)}
<div className="form-group form-group-submit col-xs-12 text-center">
<button
className="btn btn-default"
type="button"
onClick={onReset}
data-test="reset-button"
>
Reset
</button>
<button
className="btn btn-success"
type="submit"
data-test="submit-button"
>
{this.buttonText}
</button>
</div>
</form>
</div>
</form>
</div>
</div>
<div className="col-md-9">
<AlertOutputs
hash={hash}
exists={exists}
source={source}
kapacitor={kapacitor}
notify={notify}
/>
</div>
</div>
</div>
<div className="col-md-9">
<AlertOutputs
hash={hash}
exists={exists}
source={source}
kapacitor={kapacitor}
notify={notify}
/>
</div>
</div>
</FancyScrollbar>
</div>
</FancyScrollbar>
</div>
)
)
}
private get buttonText(): string {
const {exists} = this.props
if (exists) {
return 'Update'
}
return 'Connect'
}
private get headerText(): string {
const {exists} = this.props
let prefix = 'Add a New'
if (exists) {
prefix = 'Configure'
}
return `${prefix} Kapacitor Connection`
}
private get url(): string {
const {kapacitor: {url}} = this.props
if (url) {
return url
}
return ''
}
private get isSecure(): boolean {
return this.url.startsWith('https')
}
}
export default KapacitorForm

View File

@ -0,0 +1,31 @@
import React, {SFC, ChangeEvent} from 'react'
import {Kapacitor} from 'src/types'
import {insecureSkipVerifyText} from 'src/shared/copy/tooltipText'
interface Props {
kapacitor: Kapacitor
onCheckboxChange: (e: ChangeEvent<HTMLInputElement>) => void
}
const KapacitorFormSkipVerify: SFC<Props> = ({
kapacitor: {insecureSkipVerify},
onCheckboxChange,
}) => {
return (
<div className="form-group col-xs-12">
<div className="form-control-static">
<input
type="checkbox"
id="insecureSkipVerifyCheckbox"
name="insecureSkipVerify"
checked={insecureSkipVerify}
onChange={onCheckboxChange}
/>
<label htmlFor="insecureSkipVerifyCheckbox">Unsafe SSL</label>
</div>
<label className="form-helper">{insecureSkipVerifyText}</label>
</div>
)
}
export default KapacitorFormSkipVerify

View File

@ -27,7 +27,7 @@ const getLegibleTextColor = bgColorHex => {
const findNearestCrossedThreshold = (colors, lastValue) => {
const sortedColors = _.sortBy(colors, color => Number(color.value))
const nearestCrossedThreshold = sortedColors
.filter(color => lastValue > color.value)
.filter(color => lastValue >= color.value)
.pop()
return nearestCrossedThreshold

View File

@ -412,24 +412,24 @@ export const notifyDashboardDeleted = name => ({
export const notifyDashboardDeleteFailed = (name, errorMessage) =>
`Failed to delete Dashboard ${name}: ${errorMessage}.`
export const notifyCellAdded = () => ({
export const notifyCellAdded = name => ({
...defaultSuccessNotification,
icon: 'dash-h',
duration: 2200,
message: 'Added "Untitled Cell" to dashboard.',
duration: 1900,
message: `Added "${name}" to dashboard.`,
})
export const notifyCellCloned = name => ({
...defaultSuccessNotification,
icon: 'duplicate',
duration: 2200,
duration: 1900,
message: `Added "${name}" to dashboard.`,
})
export const notifyCellDeleted = name => ({
...defaultDeletionNotification,
icon: 'dash-h',
duration: 2200,
duration: 1900,
message: `Deleted "${name}" from dashboard.`,
})

View File

@ -1,33 +1,53 @@
/* tslint:disable no-console */
import React from 'react'
/*
tslint:disable no-console
tslint:disable max-classes-per-file
*/
export function ErrorHandling<
P,
S,
T extends {new (...args: any[]): React.Component<P, S>}
>(constructor: T) {
class Wrapped extends constructor {
private error: boolean = false
import React, {ComponentClass, Component} from 'react'
public componentDidCatch(error, info) {
console.error(error)
console.warn(info)
this.error = true
this.forceUpdate()
}
class DefaultError extends Component {
public render() {
return (
<p className="error">
A Chronograf error has occurred. Please report the issue&nbsp;
<a href="https://github.com/influxdata/chronograf/issues">here</a>.
</p>
)
}
}
public render() {
if (this.error) {
return (
<p className="error">
A Chronograf error has occurred. Please report the issue&nbsp;
<a href="https://github.com/influxdata/chronograf/issues">here</a>.
</p>
)
export function ErrorHandlingWith(
Error: ComponentClass, // Must be a class based component and not an SFC
alwaysDisplay = false
) {
return <P, S, T extends {new (...args: any[]): Component<P, S>}>(
constructor: T
) => {
class Wrapped extends constructor {
public static get displayName(): string {
return constructor.name
}
return super.render()
private error: boolean = false
public componentDidCatch(err, info) {
console.error(err)
console.warn(info)
this.error = true
this.forceUpdate()
}
public render() {
if (this.error || alwaysDisplay) {
return <Error />
}
return super.render()
}
}
return Wrapped
}
return Wrapped
}
export const ErrorHandling = ErrorHandlingWith(DefaultError)

View File

@ -7,7 +7,7 @@
*/
.ReactCodeMirror {
.react-codemirror2 {
position: relative;
width: 100%;
height: 100%;

View File

@ -20,7 +20,8 @@ import {
} from 'shared/constants/thresholds'
import {validateLineColors} from 'src/shared/constants/graphColorPalettes'
import {CELL_TYPE_LINE, UNTITLED_CELL_LINE} from 'src/dashboards/graphics/graph'
import {CELL_TYPE_LINE} from 'src/dashboards/graphics/graph'
import {UNTITLED_CELL_LINE} from 'src/dashboards/constants'
const defaultCellType = CELL_TYPE_LINE
const defaultCellName = UNTITLED_CELL_LINE