prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write "src/**/*.js"
parent
9d096c2afe
commit
4cbcc77fff
|
@ -7,10 +7,7 @@ import Notifications from 'shared/components/Notifications'
|
|||
|
||||
import {publishNotification} from 'src/shared/actions/notifications'
|
||||
|
||||
const {
|
||||
func,
|
||||
node,
|
||||
} = PropTypes
|
||||
const {func, node} = PropTypes
|
||||
|
||||
const App = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -29,15 +26,16 @@ const App = React.createClass({
|
|||
<div className="chronograf-root">
|
||||
<SideNav />
|
||||
<Notifications />
|
||||
{this.props.children && React.cloneElement(this.props.children, {
|
||||
addFlashMessage: this.handleAddFlashMessage,
|
||||
})}
|
||||
{this.props.children &&
|
||||
React.cloneElement(this.props.children, {
|
||||
addFlashMessage: this.handleAddFlashMessage,
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
notify: bindActionCreators(publishNotification, dispatch),
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AJAX from 'src/utils/ajax'
|
||||
|
||||
export const getUsers = async (url) => {
|
||||
export const getUsers = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
|
@ -12,7 +12,7 @@ export const getUsers = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getRoles = async (url) => {
|
||||
export const getRoles = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
|
@ -24,7 +24,7 @@ export const getRoles = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getPermissions = async (url) => {
|
||||
export const getPermissions = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
|
@ -36,7 +36,7 @@ export const getPermissions = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const getDbsAndRps = async (url) => {
|
||||
export const getDbsAndRps = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
|
@ -96,7 +96,7 @@ export const createRetentionPolicy = async (url, retentionPolicy) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deleteRetentionPolicy = async (url) => {
|
||||
export const deleteRetentionPolicy = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'DELETE',
|
||||
|
@ -107,7 +107,7 @@ export const deleteRetentionPolicy = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deleteRole = async (url) => {
|
||||
export const deleteRole = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'DELETE',
|
||||
|
@ -119,7 +119,7 @@ export const deleteRole = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deleteUser = async (url) => {
|
||||
export const deleteUser = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'DELETE',
|
||||
|
@ -131,7 +131,7 @@ export const deleteUser = async (url) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deleteDatabase = async (url) => {
|
||||
export const deleteDatabase = async url => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'DELETE',
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
import {Tab, Tabs, TabPanel, TabPanels, TabList} from 'src/shared/components/Tabs'
|
||||
import {
|
||||
Tab,
|
||||
Tabs,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
TabList,
|
||||
} from 'src/shared/components/Tabs'
|
||||
import UsersTable from 'src/admin/components/UsersTable'
|
||||
import RolesTable from 'src/admin/components/RolesTable'
|
||||
import QueriesPage from 'src/admin/containers/QueriesPage'
|
||||
|
@ -33,7 +39,7 @@ const AdminTabs = ({
|
|||
let tabs = [
|
||||
{
|
||||
type: 'DB Management',
|
||||
component: (<DatabaseManagerPage source={source} />),
|
||||
component: <DatabaseManagerPage source={source} />,
|
||||
},
|
||||
{
|
||||
type: 'Users',
|
||||
|
@ -77,7 +83,7 @@ const AdminTabs = ({
|
|||
},
|
||||
{
|
||||
type: 'Queries',
|
||||
component: (<QueriesPage source={source} />),
|
||||
component: <QueriesPage source={source} />,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -88,34 +94,30 @@ const AdminTabs = ({
|
|||
return (
|
||||
<Tabs className="row">
|
||||
<TabList customClass="col-md-2 admin-tabs">
|
||||
{
|
||||
tabs.map((t, i) => (<Tab key={tabs[i].type}>{tabs[i].type}</Tab>))
|
||||
}
|
||||
{tabs.map((t, i) => <Tab key={tabs[i].type}>{tabs[i].type}</Tab>)}
|
||||
</TabList>
|
||||
<TabPanels customClass="col-md-10">
|
||||
{
|
||||
tabs.map((t, i) => (<TabPanel key={tabs[i].type}>{t.component}</TabPanel>))
|
||||
}
|
||||
{tabs.map((t, i) => (
|
||||
<TabPanel key={tabs[i].type}>{t.component}</TabPanel>
|
||||
))}
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
AdminTabs.propTypes = {
|
||||
users: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
roles: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
})),
|
||||
users: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
roles: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
roles: arrayOf(shape()),
|
||||
source: shape(),
|
||||
permissions: arrayOf(string),
|
||||
|
|
|
@ -34,7 +34,7 @@ class ChangePassRow extends Component {
|
|||
}
|
||||
|
||||
handleKeyPress(user) {
|
||||
return (e) => {
|
||||
return e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.handleSubmit(user)
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ class ChangePassRow extends Component {
|
|||
}
|
||||
|
||||
handleEdit(user) {
|
||||
return (e) => {
|
||||
return e => {
|
||||
this.props.onEdit(user, {[e.target.name]: e.target.value})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,23 +44,22 @@ class DatabaseRow extends Component {
|
|||
if (isEditing) {
|
||||
return (
|
||||
<tr>
|
||||
<td>{
|
||||
isNew ?
|
||||
<div className="admin-table--edit-cell">
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
defaultValue={name}
|
||||
placeholder="Name this RP"
|
||||
onKeyDown={(e) => this.handleKeyDown(e, database)}
|
||||
ref={(r) => this.name = r}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div> :
|
||||
<div className="admin-table--edit-cell">
|
||||
{name}
|
||||
</div>
|
||||
}
|
||||
<td>
|
||||
{isNew
|
||||
? <div className="admin-table--edit-cell">
|
||||
<input
|
||||
className="form-control"
|
||||
type="text"
|
||||
defaultValue={name}
|
||||
placeholder="Name this RP"
|
||||
onKeyDown={e => this.handleKeyDown(e, database)}
|
||||
ref={r => (this.name = r)}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
: <div className="admin-table--edit-cell">
|
||||
{name}
|
||||
</div>}
|
||||
</td>
|
||||
<td>
|
||||
<div className="admin-table--edit-cell">
|
||||
|
@ -70,8 +69,8 @@ class DatabaseRow extends Component {
|
|||
type="text"
|
||||
defaultValue={formattedDuration}
|
||||
placeholder="How long should Data last"
|
||||
onKeyDown={(e) => this.handleKeyDown(e, database)}
|
||||
ref={(r) => this.duration = r}
|
||||
onKeyDown={e => this.handleKeyDown(e, database)}
|
||||
ref={r => (this.duration = r)}
|
||||
autoFocus={!isNew}
|
||||
/>
|
||||
</div>
|
||||
|
@ -85,15 +84,19 @@ class DatabaseRow extends Component {
|
|||
min="1"
|
||||
defaultValue={replication || 1}
|
||||
placeholder="# of Nodes"
|
||||
onKeyDown={(e) => this.handleKeyDown(e, database)}
|
||||
ref={(r) => this.replication = r}
|
||||
onKeyDown={e => this.handleKeyDown(e, database)}
|
||||
ref={r => (this.replication = r)}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right">
|
||||
<YesNoButtons
|
||||
onConfirm={isNew ? this.handleCreate : this.handleUpdate}
|
||||
onCancel={isNew ? () => onRemove(database, retentionPolicy) : this.handleEndEdit}
|
||||
onCancel={
|
||||
isNew
|
||||
? () => onRemove(database, retentionPolicy)
|
||||
: this.handleEndEdit
|
||||
}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -102,21 +105,30 @@ class DatabaseRow extends Component {
|
|||
|
||||
return (
|
||||
<tr>
|
||||
<td>{name} {isDefault ? <span className="default-source-label">default</span> : null}</td>
|
||||
<td>
|
||||
{name}
|
||||
{' '}
|
||||
{isDefault
|
||||
? <span className="default-source-label">default</span>
|
||||
: null}
|
||||
</td>
|
||||
<td onClick={this.handleStartEdit}>{formattedDuration}</td>
|
||||
{isRFDisplayed ? <td onClick={this.handleStartEdit}>{replication}</td> : null}
|
||||
{isRFDisplayed
|
||||
? <td onClick={this.handleStartEdit}>{replication}</td>
|
||||
: null}
|
||||
<td className="text-right">
|
||||
{
|
||||
isDeleting ?
|
||||
<YesNoButtons
|
||||
{isDeleting
|
||||
? <YesNoButtons
|
||||
onConfirm={() => onDelete(database, retentionPolicy)}
|
||||
onCancel={this.handleEndDelete} /> :
|
||||
<button
|
||||
onCancel={this.handleEndDelete}
|
||||
/>
|
||||
: <button
|
||||
className="btn btn-xs btn-danger admin-table--hidden"
|
||||
style={isDeletable ? {} : {visibility: 'hidden'}}
|
||||
onClick={this.handleStartDelete}>{`Delete ${name}`}
|
||||
</button>
|
||||
}
|
||||
onClick={this.handleStartDelete}
|
||||
>
|
||||
{`Delete ${name}`}
|
||||
</button>}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
@ -174,7 +186,6 @@ class DatabaseRow extends Component {
|
|||
const {key} = e
|
||||
const {retentionPolicy, database, onRemove} = this.props
|
||||
|
||||
|
||||
if (key === 'Escape') {
|
||||
if (retentionPolicy.isNew) {
|
||||
onRemove(database, retentionPolicy)
|
||||
|
@ -196,7 +207,7 @@ class DatabaseRow extends Component {
|
|||
|
||||
getInputValues() {
|
||||
const {notify, retentionPolicy: {name: currentName}} = this.props
|
||||
const name = this.name && this.name.value.trim() || currentName
|
||||
const name = (this.name && this.name.value.trim()) || currentName
|
||||
let duration = this.duration.value.trim()
|
||||
const replication = +this.replication.value.trim()
|
||||
|
||||
|
@ -215,16 +226,9 @@ class DatabaseRow extends Component {
|
|||
replication,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const {
|
||||
bool,
|
||||
func,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {bool, func, number, shape, string} = PropTypes
|
||||
|
||||
DatabaseRow.propTypes = {
|
||||
retentionPolicy: shape({
|
||||
|
|
|
@ -5,11 +5,7 @@ import _ from 'lodash'
|
|||
import DatabaseRow from 'src/admin/components/DatabaseRow'
|
||||
import DatabaseTableHeader from 'src/admin/components/DatabaseTableHeader'
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
bool,
|
||||
} = PropTypes
|
||||
const {func, shape, bool} = PropTypes
|
||||
|
||||
const DatabaseTable = ({
|
||||
database,
|
||||
|
@ -53,28 +49,28 @@ const DatabaseTable = ({
|
|||
<th>Retention Policy</th>
|
||||
<th>Duration</th>
|
||||
{isRFDisplayed ? <th>Replication Factor</th> : null}
|
||||
<th></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
_.sortBy(database.retentionPolicies, ({name}) => name.toLowerCase()).map(rp => {
|
||||
return (
|
||||
<DatabaseRow
|
||||
key={rp.links.self}
|
||||
notify={notify}
|
||||
database={database}
|
||||
retentionPolicy={rp}
|
||||
onCreate={onCreateRetentionPolicy}
|
||||
onUpdate={onUpdateRetentionPolicy}
|
||||
onRemove={onRemoveRetentionPolicy}
|
||||
onDelete={onDeleteRetentionPolicy}
|
||||
isRFDisplayed={isRFDisplayed}
|
||||
isDeletable={database.retentionPolicies.length > 1}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
{_.sortBy(database.retentionPolicies, ({name}) =>
|
||||
name.toLowerCase()
|
||||
).map(rp => {
|
||||
return (
|
||||
<DatabaseRow
|
||||
key={rp.links.self}
|
||||
notify={notify}
|
||||
database={database}
|
||||
retentionPolicy={rp}
|
||||
onCreate={onCreateRetentionPolicy}
|
||||
onUpdate={onUpdateRetentionPolicy}
|
||||
onRemove={onRemoveRetentionPolicy}
|
||||
onDelete={onDeleteRetentionPolicy}
|
||||
isRFDisplayed={isRFDisplayed}
|
||||
isDeletable={database.retentionPolicies.length > 1}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -60,19 +60,25 @@ const Header = ({
|
|||
|
||||
const buttons = (
|
||||
<div className="text-right db-manager-header--actions">
|
||||
<button className="btn btn-xs btn-primary" disabled={isAddRPDisabled} onClick={() => onAddRetentionPolicy(database)}>
|
||||
<button
|
||||
className="btn btn-xs btn-primary"
|
||||
disabled={isAddRPDisabled}
|
||||
onClick={() => onAddRetentionPolicy(database)}
|
||||
>
|
||||
Add Retention Policy
|
||||
</button>
|
||||
{
|
||||
database.name === '_internal' ? null :
|
||||
<button className="btn btn-xs btn-danger" onClick={() => onStartDelete(database)}>
|
||||
{database.name === '_internal'
|
||||
? null
|
||||
: <button
|
||||
className="btn btn-xs btn-danger"
|
||||
onClick={() => onStartDelete(database)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
}
|
||||
</button>}
|
||||
</div>
|
||||
)
|
||||
|
||||
const onConfirm = (db) => {
|
||||
const onConfirm = db => {
|
||||
if (database.deleteCode !== `DELETE ${database.name}`) {
|
||||
return notify('error', `Type DELETE ${database.name} to confirm`)
|
||||
}
|
||||
|
@ -89,12 +95,16 @@ const Header = ({
|
|||
type="text"
|
||||
value={database.deleteCode || ''}
|
||||
placeholder={`DELETE ${database.name}`}
|
||||
onChange={(e) => onDatabaseDeleteConfirm(database, e)}
|
||||
onKeyDown={(e) => onDatabaseDeleteConfirm(database, e)}
|
||||
onChange={e => onDatabaseDeleteConfirm(database, e)}
|
||||
onKeyDown={e => onDatabaseDeleteConfirm(database, e)}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</div>
|
||||
<ConfirmButtons item={database} onConfirm={onConfirm} onCancel={onCancel} />
|
||||
<ConfirmButtons
|
||||
item={database}
|
||||
onConfirm={onConfirm}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
@ -114,19 +124,15 @@ const EditHeader = ({database, onEdit, onKeyDown, onConfirm, onCancel}) => (
|
|||
type="text"
|
||||
value={database.name}
|
||||
placeholder="Name this database"
|
||||
onChange={(e) => onEdit(database, {name: e.target.value})}
|
||||
onKeyDown={(e) => onKeyDown(e, database)}
|
||||
onChange={e => onEdit(database, {name: e.target.value})}
|
||||
onKeyDown={e => onKeyDown(e, database)}
|
||||
autoFocus={true}
|
||||
/>
|
||||
<ConfirmButtons item={database} onConfirm={onConfirm} onCancel={onCancel} />
|
||||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
bool,
|
||||
} = PropTypes
|
||||
const {func, shape, bool} = PropTypes
|
||||
|
||||
DatabaseTableHeader.propTypes = {
|
||||
onEdit: func,
|
||||
|
@ -143,7 +149,6 @@ DatabaseTableHeader.propTypes = {
|
|||
isAddRPDisabled: bool,
|
||||
}
|
||||
|
||||
|
||||
Header.propTypes = {
|
||||
notify: func,
|
||||
onConfirm: func,
|
||||
|
|
|
@ -3,17 +3,15 @@ import React, {PropTypes} from 'react'
|
|||
const EmptyRow = ({tableName}) => (
|
||||
<tr className="table-empty-state">
|
||||
<th colSpan="5">
|
||||
<p>You don't have any {tableName},<br/>why not create one?</p>
|
||||
<p>You don't have any {tableName},<br />why not create one?</p>
|
||||
</th>
|
||||
</tr>
|
||||
)
|
||||
|
||||
const {
|
||||
string,
|
||||
} = PropTypes
|
||||
const {string} = PropTypes
|
||||
|
||||
EmptyRow.propTypes = {
|
||||
tableName: string.isRequired,
|
||||
}
|
||||
|
||||
export default EmptyRow
|
||||
export default EmptyRow
|
||||
|
|
|
@ -37,20 +37,22 @@ class FilterBar extends Component {
|
|||
onChange={this.handleText}
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
<span className="icon search" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
<button className="btn btn-sm btn-primary" disabled={isEditing} onClick={() => onClickCreate(type)}>Create {placeholderText.substring(0, placeholderText.length - 1)}</button>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
disabled={isEditing}
|
||||
onClick={() => onClickCreate(type)}
|
||||
>
|
||||
Create {placeholderText.substring(0, placeholderText.length - 1)}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
bool,
|
||||
func,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {bool, func, string} = PropTypes
|
||||
|
||||
FilterBar.propTypes = {
|
||||
onFilter: func.isRequired,
|
||||
|
|
|
@ -12,11 +12,13 @@ const QueriesTable = ({queries, onKillQuery}) => (
|
|||
<th>Database</th>
|
||||
<th>Query</th>
|
||||
<th>Running</th>
|
||||
<th></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{queries.map((q) => <QueryRow key={q.id} query={q} onKill={onKillQuery}/>)}
|
||||
{queries.map(q => (
|
||||
<QueryRow key={q.id} query={q} onKill={onKillQuery} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -24,11 +26,7 @@ const QueriesTable = ({queries, onKillQuery}) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, shape} = PropTypes
|
||||
|
||||
QueriesTable.propTypes = {
|
||||
queries: arrayOf(shape()),
|
||||
|
|
|
@ -36,20 +36,24 @@ class QueryRow extends Component {
|
|||
<td><code>{query}</code></td>
|
||||
<td>{duration}</td>
|
||||
<td className="admin-table--kill-button text-right">
|
||||
{ this.state.confirmingKill ?
|
||||
<ConfirmButtons onConfirm={this.handleFinishHim} onCancel={this.handleShowMercy} /> :
|
||||
<button className="btn btn-xs btn-danger admin-table--hidden" onClick={this.handleInitiateKill}>Kill</button>
|
||||
}
|
||||
{this.state.confirmingKill
|
||||
? <ConfirmButtons
|
||||
onConfirm={this.handleFinishHim}
|
||||
onCancel={this.handleShowMercy}
|
||||
/>
|
||||
: <button
|
||||
className="btn btn-xs btn-danger admin-table--hidden"
|
||||
onClick={this.handleInitiateKill}
|
||||
>
|
||||
Kill
|
||||
</button>}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
} = PropTypes
|
||||
const {func, shape} = PropTypes
|
||||
|
||||
QueryRow.propTypes = {
|
||||
query: shape().isRequired,
|
||||
|
|
|
@ -9,7 +9,7 @@ class RoleEditingRow extends Component {
|
|||
}
|
||||
|
||||
handleKeyPress(role) {
|
||||
return (e) => {
|
||||
return e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.props.onSave(role)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class RoleEditingRow extends Component {
|
|||
}
|
||||
|
||||
handleEdit(role) {
|
||||
return (e) => {
|
||||
return e => {
|
||||
this.props.onEdit(role, {[e.target.name]: e.target.value})
|
||||
}
|
||||
}
|
||||
|
@ -43,11 +43,7 @@ class RoleEditingRow extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
} = PropTypes
|
||||
const {bool, func, shape} = PropTypes
|
||||
|
||||
RoleEditingRow.propTypes = {
|
||||
role: shape().isRequired,
|
||||
|
|
|
@ -21,11 +21,11 @@ const RoleRow = ({
|
|||
onUpdateRoleUsers,
|
||||
onUpdateRolePermissions,
|
||||
}) => {
|
||||
const handleUpdateUsers = (u) => {
|
||||
onUpdateRoleUsers(role, u.map((n) => ({name: n})))
|
||||
const handleUpdateUsers = u => {
|
||||
onUpdateRoleUsers(role, u.map(n => ({name: n})))
|
||||
}
|
||||
|
||||
const handleUpdatePermissions = (allowed) => {
|
||||
const handleUpdatePermissions = allowed => {
|
||||
onUpdateRolePermissions(role, [{scope: 'all', allowed}])
|
||||
}
|
||||
|
||||
|
@ -34,9 +34,14 @@ const RoleRow = ({
|
|||
if (isEditing) {
|
||||
return (
|
||||
<tr className="admin-table--edit-row">
|
||||
<RoleEditingRow role={role} onEdit={onEdit} onSave={onSave} isNew={isNew} />
|
||||
<td></td>
|
||||
<td></td>
|
||||
<RoleEditingRow
|
||||
role={role}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
isNew={isNew}
|
||||
/>
|
||||
<td />
|
||||
<td />
|
||||
<td className="text-right" style={{width: '85px'}}>
|
||||
<ConfirmButtons item={role} onConfirm={onSave} onCancel={onCancel} />
|
||||
</td>
|
||||
|
@ -48,49 +53,45 @@ const RoleRow = ({
|
|||
<tr>
|
||||
<td>{name}</td>
|
||||
<td>
|
||||
{
|
||||
allPermissions && allPermissions.length ?
|
||||
<MultiSelectDropdown
|
||||
{allPermissions && allPermissions.length
|
||||
? <MultiSelectDropdown
|
||||
items={allPermissions}
|
||||
selectedItems={perms}
|
||||
label={perms.length ? '' : 'Select Permissions'}
|
||||
onApply={handleUpdatePermissions}
|
||||
/> : null
|
||||
}
|
||||
/>
|
||||
: null}
|
||||
</td>
|
||||
<td>
|
||||
{
|
||||
allUsers && allUsers.length ?
|
||||
<MultiSelectDropdown
|
||||
items={allUsers.map((u) => u.name)}
|
||||
selectedItems={users === undefined ? [] : users.map((u) => u.name)}
|
||||
{allUsers && allUsers.length
|
||||
? <MultiSelectDropdown
|
||||
items={allUsers.map(u => u.name)}
|
||||
selectedItems={users === undefined ? [] : users.map(u => u.name)}
|
||||
label={users && users.length ? '' : 'Select Users'}
|
||||
onApply={handleUpdateUsers}
|
||||
/> : null
|
||||
}
|
||||
/>
|
||||
: null}
|
||||
</td>
|
||||
<DeleteConfirmTableCell onDelete={onDelete} item={role} />
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
RoleRow.propTypes = {
|
||||
role: shape({
|
||||
name: string,
|
||||
permissions: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
users: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
permissions: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
users: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
}).isRequired,
|
||||
isNew: bool,
|
||||
isEditing: bool,
|
||||
|
|
|
@ -18,7 +18,12 @@ const RolesTable = ({
|
|||
onUpdateRolePermissions,
|
||||
}) => (
|
||||
<div className="panel panel-info">
|
||||
<FilterBar type="roles" onFilter={onFilter} isEditing={isEditing} onClickCreate={onClickCreate} />
|
||||
<FilterBar
|
||||
type="roles"
|
||||
onFilter={onFilter}
|
||||
isEditing={isEditing}
|
||||
onClickCreate={onClickCreate}
|
||||
/>
|
||||
<div className="panel-body">
|
||||
<table className="table v-center admin-table">
|
||||
<thead>
|
||||
|
@ -26,54 +31,55 @@ const RolesTable = ({
|
|||
<th>Name</th>
|
||||
<th>Permissions</th>
|
||||
<th>Users</th>
|
||||
<th></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
roles.length ?
|
||||
roles.filter(r => !r.hidden).map((role) =>
|
||||
<RoleRow
|
||||
key={role.links.self}
|
||||
allUsers={allUsers}
|
||||
allPermissions={permissions}
|
||||
role={role}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDelete={onDelete}
|
||||
onUpdateRoleUsers={onUpdateRoleUsers}
|
||||
onUpdateRolePermissions={onUpdateRolePermissions}
|
||||
isEditing={role.isEditing}
|
||||
isNew={role.isNew}
|
||||
/>
|
||||
) : <EmptyRow tableName={'Roles'} />
|
||||
}
|
||||
{roles.length
|
||||
? roles
|
||||
.filter(r => !r.hidden)
|
||||
.map(role => (
|
||||
<RoleRow
|
||||
key={role.links.self}
|
||||
allUsers={allUsers}
|
||||
allPermissions={permissions}
|
||||
role={role}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDelete={onDelete}
|
||||
onUpdateRoleUsers={onUpdateRoleUsers}
|
||||
onUpdateRolePermissions={onUpdateRolePermissions}
|
||||
isEditing={role.isEditing}
|
||||
isNew={role.isNew}
|
||||
/>
|
||||
))
|
||||
: <EmptyRow tableName={'Roles'} />}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
RolesTable.propTypes = {
|
||||
roles: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
permissions: arrayOf(shape({
|
||||
name: string,
|
||||
scope: string.isRequired,
|
||||
})),
|
||||
users: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
})),
|
||||
roles: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
permissions: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
scope: string.isRequired,
|
||||
})
|
||||
),
|
||||
users: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
isEditing: bool,
|
||||
onClickCreate: func.isRequired,
|
||||
onEdit: func.isRequired,
|
||||
|
|
|
@ -9,7 +9,7 @@ class UserEditingRow extends Component {
|
|||
}
|
||||
|
||||
handleKeyPress(user) {
|
||||
return (e) => {
|
||||
return e => {
|
||||
if (e.key === 'Enter') {
|
||||
this.props.onSave(user)
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ class UserEditingRow extends Component {
|
|||
}
|
||||
|
||||
handleEdit(user) {
|
||||
return (e) => {
|
||||
return e => {
|
||||
this.props.onEdit(user, {[e.target.name]: e.target.value})
|
||||
}
|
||||
}
|
||||
|
@ -37,9 +37,8 @@ class UserEditingRow extends Component {
|
|||
onKeyPress={this.handleKeyPress(user)}
|
||||
autoFocus={true}
|
||||
/>
|
||||
{
|
||||
isNew ?
|
||||
<input
|
||||
{isNew
|
||||
? <input
|
||||
className="form-control"
|
||||
name="password"
|
||||
type="password"
|
||||
|
@ -47,20 +46,15 @@ class UserEditingRow extends Component {
|
|||
placeholder="Password"
|
||||
onChange={this.handleEdit(user)}
|
||||
onKeyPress={this.handleKeyPress(user)}
|
||||
/> :
|
||||
null
|
||||
}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
</td>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
} = PropTypes
|
||||
const {bool, func, shape} = PropTypes
|
||||
|
||||
UserEditingRow.propTypes = {
|
||||
user: shape().isRequired,
|
||||
|
|
|
@ -24,12 +24,15 @@ const UserRow = ({
|
|||
onUpdateRoles,
|
||||
onUpdatePassword,
|
||||
}) => {
|
||||
const handleUpdatePermissions = (allowed) => {
|
||||
const handleUpdatePermissions = allowed => {
|
||||
onUpdatePermissions(user, [{scope: 'all', allowed}])
|
||||
}
|
||||
|
||||
const handleUpdateRoles = (roleNames) => {
|
||||
onUpdateRoles(user, allRoles.filter(r => roleNames.find(rn => rn === r.name)))
|
||||
const handleUpdateRoles = roleNames => {
|
||||
onUpdateRoles(
|
||||
user,
|
||||
allRoles.filter(r => roleNames.find(rn => rn === r.name))
|
||||
)
|
||||
}
|
||||
|
||||
const handleUpdatePassword = () => {
|
||||
|
@ -39,9 +42,14 @@ const UserRow = ({
|
|||
if (isEditing) {
|
||||
return (
|
||||
<tr className="admin-table--edit-row">
|
||||
<UserEditingRow user={user} onEdit={onEdit} onSave={onSave} isNew={isNew} />
|
||||
{hasRoles ? <td></td> : null}
|
||||
<td></td>
|
||||
<UserEditingRow
|
||||
user={user}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
isNew={isNew}
|
||||
/>
|
||||
{hasRoles ? <td /> : null}
|
||||
<td />
|
||||
<td className="text-right" style={{width: '85px'}}>
|
||||
<ConfirmButtons item={user} onConfirm={onSave} onCancel={onCancel} />
|
||||
</td>
|
||||
|
@ -52,54 +60,59 @@ const UserRow = ({
|
|||
return (
|
||||
<tr>
|
||||
<td>{name}</td>
|
||||
{
|
||||
hasRoles ?
|
||||
<td>
|
||||
{hasRoles
|
||||
? <td>
|
||||
<MultiSelectDropdown
|
||||
items={allRoles.map((r) => r.name)}
|
||||
selectedItems={roles ? roles.map((r) => r.name) : []/* TODO remove check when server returns empty list */}
|
||||
items={allRoles.map(r => r.name)}
|
||||
selectedItems={
|
||||
roles
|
||||
? roles.map(r => r.name)
|
||||
: [] /* TODO remove check when server returns empty list */
|
||||
}
|
||||
label={roles && roles.length ? '' : 'Select Roles'}
|
||||
onApply={handleUpdateRoles}
|
||||
/>
|
||||
</td> :
|
||||
null
|
||||
}
|
||||
</td>
|
||||
: null}
|
||||
<td>
|
||||
{
|
||||
allPermissions && allPermissions.length ?
|
||||
<MultiSelectDropdown
|
||||
{allPermissions && allPermissions.length
|
||||
? <MultiSelectDropdown
|
||||
items={allPermissions}
|
||||
selectedItems={_.get(permissions, ['0', 'allowed'], [])}
|
||||
label={permissions && permissions.length ? '' : 'Select Permissions'}
|
||||
label={
|
||||
permissions && permissions.length ? '' : 'Select Permissions'
|
||||
}
|
||||
onApply={handleUpdatePermissions}
|
||||
/> : null
|
||||
}
|
||||
/>
|
||||
: null}
|
||||
</td>
|
||||
<td className="text-right" style={{width: '300px'}}>
|
||||
<ChangePassRow onEdit={onEdit} onApply={handleUpdatePassword} user={user} />
|
||||
<ChangePassRow
|
||||
onEdit={onEdit}
|
||||
onApply={handleUpdatePassword}
|
||||
user={user}
|
||||
/>
|
||||
</td>
|
||||
<DeleteConfirmTableCell onDelete={onDelete} item={user} />
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
UserRow.propTypes = {
|
||||
user: shape({
|
||||
name: string,
|
||||
roles: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
permissions: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
roles: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
permissions: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
password: string,
|
||||
}).isRequired,
|
||||
allRoles: arrayOf(shape()),
|
||||
|
|
|
@ -21,7 +21,12 @@ const UsersTable = ({
|
|||
onUpdatePassword,
|
||||
}) => (
|
||||
<div className="panel panel-info">
|
||||
<FilterBar type="users" onFilter={onFilter} isEditing={isEditing} onClickCreate={onClickCreate} />
|
||||
<FilterBar
|
||||
type="users"
|
||||
onFilter={onFilter}
|
||||
isEditing={isEditing}
|
||||
onClickCreate={onClickCreate}
|
||||
/>
|
||||
<div className="panel-body">
|
||||
<table className="table v-center admin-table">
|
||||
<thead>
|
||||
|
@ -29,57 +34,58 @@ const UsersTable = ({
|
|||
<th>User</th>
|
||||
{hasRoles && <th>Roles</th>}
|
||||
<th>Permissions</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th />
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
users.length ?
|
||||
users.filter(u => !u.hidden).map(user =>
|
||||
<UserRow
|
||||
key={user.links.self}
|
||||
user={user}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDelete={onDelete}
|
||||
isEditing={user.isEditing}
|
||||
isNew={user.isNew}
|
||||
allRoles={allRoles}
|
||||
hasRoles={hasRoles}
|
||||
allPermissions={permissions}
|
||||
onUpdatePermissions={onUpdatePermissions}
|
||||
onUpdateRoles={onUpdateRoles}
|
||||
onUpdatePassword={onUpdatePassword}
|
||||
/>) :
|
||||
<EmptyRow tableName={'Users'} />
|
||||
}
|
||||
{users.length
|
||||
? users
|
||||
.filter(u => !u.hidden)
|
||||
.map(user => (
|
||||
<UserRow
|
||||
key={user.links.self}
|
||||
user={user}
|
||||
onEdit={onEdit}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
onDelete={onDelete}
|
||||
isEditing={user.isEditing}
|
||||
isNew={user.isNew}
|
||||
allRoles={allRoles}
|
||||
hasRoles={hasRoles}
|
||||
allPermissions={permissions}
|
||||
onUpdatePermissions={onUpdatePermissions}
|
||||
onUpdateRoles={onUpdateRoles}
|
||||
onUpdatePassword={onUpdatePassword}
|
||||
/>
|
||||
))
|
||||
: <EmptyRow tableName={'Users'} />}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
UsersTable.propTypes = {
|
||||
users: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
roles: arrayOf(shape({
|
||||
name: string,
|
||||
})),
|
||||
permissions: arrayOf(shape({
|
||||
name: string,
|
||||
scope: string.isRequired,
|
||||
})),
|
||||
})),
|
||||
users: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
roles: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
})
|
||||
),
|
||||
permissions: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
scope: string.isRequired,
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
isEditing: bool,
|
||||
onClickCreate: func.isRequired,
|
||||
onEdit: func.isRequired,
|
||||
|
|
|
@ -28,14 +28,14 @@ import AdminTabs from 'src/admin/components/AdminTabs'
|
|||
|
||||
import {publishAutoDismissingNotification} from 'shared/dispatchers'
|
||||
|
||||
const isValidUser = (user) => {
|
||||
const isValidUser = user => {
|
||||
const minLen = 3
|
||||
return (user.name.length >= minLen && user.password.length >= minLen)
|
||||
return user.name.length >= minLen && user.password.length >= minLen
|
||||
}
|
||||
|
||||
const isValidRole = (role) => {
|
||||
const isValidRole = role => {
|
||||
const minLen = 3
|
||||
return (role.name.length >= minLen)
|
||||
return role.name.length >= minLen
|
||||
}
|
||||
|
||||
class AdminPage extends Component {
|
||||
|
@ -147,9 +147,16 @@ class AdminPage extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {users, roles, source, permissions, filterUsers, filterRoles} = this.props
|
||||
const {
|
||||
users,
|
||||
roles,
|
||||
source,
|
||||
permissions,
|
||||
filterUsers,
|
||||
filterRoles,
|
||||
} = this.props
|
||||
const hasRoles = !!source.links.roles
|
||||
const globalPermissions = permissions.find((p) => p.scope === 'all')
|
||||
const globalPermissions = permissions.find(p => p.scope === 'all')
|
||||
const allowed = globalPermissions ? globalPermissions.allowed : []
|
||||
|
||||
return (
|
||||
|
@ -166,9 +173,8 @@ class AdminPage extends Component {
|
|||
<div className="page-contents">
|
||||
<div className="container-fluid">
|
||||
<div className="row">
|
||||
{
|
||||
users ?
|
||||
<AdminTabs
|
||||
{users
|
||||
? <AdminTabs
|
||||
users={users}
|
||||
roles={roles}
|
||||
source={source}
|
||||
|
@ -192,9 +198,8 @@ class AdminPage extends Component {
|
|||
onUpdateUserPermissions={this.handleUpdateUserPermissions}
|
||||
onUpdateUserRoles={this.handleUpdateUserRoles}
|
||||
onUpdateUserPassword={this.handleUpdateUserPassword}
|
||||
/> :
|
||||
<span>Loading...</span>
|
||||
}
|
||||
/>
|
||||
: <span>Loading...</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -203,12 +208,7 @@ class AdminPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
|
||||
AdminPage.propTypes = {
|
||||
source: shape({
|
||||
|
@ -249,7 +249,7 @@ const mapStateToProps = ({admin: {users, roles, permissions}}) => ({
|
|||
permissions,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
loadUsers: bindActionCreators(loadUsersAsync, dispatch),
|
||||
loadRoles: bindActionCreators(loadRolesAsync, dispatch),
|
||||
loadPermissions: bindActionCreators(loadPermissionsAsync, dispatch),
|
||||
|
@ -266,8 +266,14 @@ const mapDispatchToProps = (dispatch) => ({
|
|||
filterUsers: bindActionCreators(filterUsersAction, dispatch),
|
||||
filterRoles: bindActionCreators(filterRolesAction, dispatch),
|
||||
updateRoleUsers: bindActionCreators(updateRoleUsersAsync, dispatch),
|
||||
updateRolePermissions: bindActionCreators(updateRolePermissionsAsync, dispatch),
|
||||
updateUserPermissions: bindActionCreators(updateUserPermissionsAsync, dispatch),
|
||||
updateRolePermissions: bindActionCreators(
|
||||
updateRolePermissionsAsync,
|
||||
dispatch
|
||||
),
|
||||
updateUserPermissions: bindActionCreators(
|
||||
updateUserPermissionsAsync,
|
||||
dispatch
|
||||
),
|
||||
updateUserRoles: bindActionCreators(updateUserRolesAsync, dispatch),
|
||||
updateUserPassword: bindActionCreators(updateUserPasswordAsync, dispatch),
|
||||
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
|
||||
|
|
|
@ -24,7 +24,6 @@ class DatabaseManagerPage extends Component {
|
|||
render() {
|
||||
const {source, databases, actions, notify} = this.props
|
||||
return (
|
||||
|
||||
<DatabaseManager
|
||||
databases={databases}
|
||||
notify={notify}
|
||||
|
@ -95,14 +94,7 @@ class DatabaseManagerPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
DatabaseManagerPage.propTypes = {
|
||||
source: shape({
|
||||
|
@ -110,16 +102,22 @@ DatabaseManagerPage.propTypes = {
|
|||
proxy: string,
|
||||
}),
|
||||
}),
|
||||
databases: arrayOf(shape({
|
||||
name: string,
|
||||
isEditing: bool,
|
||||
})),
|
||||
retentionPolicies: arrayOf(arrayOf(shape({
|
||||
name: string,
|
||||
duration: string,
|
||||
replication: number,
|
||||
isDefault: bool,
|
||||
}))),
|
||||
databases: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
isEditing: bool,
|
||||
})
|
||||
),
|
||||
retentionPolicies: arrayOf(
|
||||
arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
duration: string,
|
||||
replication: number,
|
||||
isDefault: bool,
|
||||
})
|
||||
)
|
||||
),
|
||||
actions: shape({
|
||||
addRetentionPolicy: func,
|
||||
loadDBsAndRPsAsync: func,
|
||||
|
@ -140,7 +138,7 @@ const mapStateToProps = ({admin: {databases, retentionPolicies}}) => ({
|
|||
retentionPolicies,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
actions: bindActionCreators(adminActionCreators, dispatch),
|
||||
notify: bindActionCreators(publishAutoDismissingNotification, dispatch),
|
||||
})
|
||||
|
|
|
@ -5,10 +5,7 @@ import {bindActionCreators} from 'redux'
|
|||
import flatten from 'lodash/flatten'
|
||||
import uniqBy from 'lodash/uniqBy'
|
||||
|
||||
import {
|
||||
showDatabases,
|
||||
showQueries,
|
||||
} from 'shared/apis/metaQuery'
|
||||
import {showDatabases, showQueries} from 'shared/apis/metaQuery'
|
||||
|
||||
import QueriesTable from 'src/admin/components/QueriesTable'
|
||||
import showDatabasesParser from 'shared/parsing/showDatabases'
|
||||
|
@ -42,39 +39,37 @@ class QueriesPage extends Component {
|
|||
render() {
|
||||
const {queries} = this.props
|
||||
|
||||
return (
|
||||
<QueriesTable queries={queries} onKillQuery={this.handleKillQuery} />
|
||||
)
|
||||
return <QueriesTable queries={queries} onKillQuery={this.handleKillQuery} />
|
||||
}
|
||||
|
||||
updateQueries() {
|
||||
const {source, notify, loadQueries} = this.props
|
||||
showDatabases(source.links.proxy).then((resp) => {
|
||||
showDatabases(source.links.proxy).then(resp => {
|
||||
const {databases, errors} = showDatabasesParser(resp.data)
|
||||
if (errors.length) {
|
||||
errors.forEach((message) => notify('error', message))
|
||||
errors.forEach(message => notify('error', message))
|
||||
return
|
||||
}
|
||||
|
||||
const fetches = databases.map((db) => showQueries(source.links.proxy, db))
|
||||
const fetches = databases.map(db => showQueries(source.links.proxy, db))
|
||||
|
||||
Promise.all(fetches).then((queryResponses) => {
|
||||
Promise.all(fetches).then(queryResponses => {
|
||||
const allQueries = []
|
||||
queryResponses.forEach((queryResponse) => {
|
||||
queryResponses.forEach(queryResponse => {
|
||||
const result = showQueriesParser(queryResponse.data)
|
||||
if (result.errors.length) {
|
||||
result.errors.forEach((message) => notify('error', message))
|
||||
result.errors.forEach(message => notify('error', message))
|
||||
}
|
||||
|
||||
allQueries.push(...result.queries)
|
||||
})
|
||||
|
||||
const queries = uniqBy(flatten(allQueries), (q) => q.id)
|
||||
const queries = uniqBy(flatten(allQueries), q => q.id)
|
||||
|
||||
// sorting queries by magnitude, so generally longer queries will appear atop the list
|
||||
const sortedQueries = queries.sort((a, b) => {
|
||||
const aTime = TIMES.find((t) => a.duration.match(t.test))
|
||||
const bTime = TIMES.find((t) => b.duration.match(t.test))
|
||||
const aTime = TIMES.find(t => a.duration.match(t.test))
|
||||
const bTime = TIMES.find(t => b.duration.match(t.test))
|
||||
return +aTime.magnitude <= +bTime.magnitude
|
||||
})
|
||||
|
||||
|
@ -89,12 +84,7 @@ class QueriesPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
string,
|
||||
shape,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, string, shape} = PropTypes
|
||||
|
||||
QueriesPage.propTypes = {
|
||||
source: shape({
|
||||
|
@ -115,7 +105,7 @@ const mapStateToProps = ({admin: {queries, queryIDToKill}}) => ({
|
|||
queryIDToKill,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
loadQueries: bindActionCreators(loadQueriesAction, dispatch),
|
||||
setQueryToKill: bindActionCreators(setQueryToKillAction, dispatch),
|
||||
killQuery: bindActionCreators(killQueryAsync, dispatch),
|
||||
|
|
|
@ -37,10 +37,7 @@ export default function admin(state = initialState, action) {
|
|||
const newUser = {...NEW_DEFAULT_USER, isEditing: true}
|
||||
return {
|
||||
...state,
|
||||
users: [
|
||||
newUser,
|
||||
...state.users,
|
||||
],
|
||||
users: [newUser, ...state.users],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,10 +45,7 @@ export default function admin(state = initialState, action) {
|
|||
const newRole = {...NEW_DEFAULT_ROLE, isEditing: true}
|
||||
return {
|
||||
...state,
|
||||
roles: [
|
||||
newRole,
|
||||
...state.roles,
|
||||
],
|
||||
roles: [newRole, ...state.roles],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,19 +54,23 @@ export default function admin(state = initialState, action) {
|
|||
|
||||
return {
|
||||
...state,
|
||||
databases: [
|
||||
newDatabase,
|
||||
...state.databases,
|
||||
],
|
||||
databases: [newDatabase, ...state.databases],
|
||||
}
|
||||
}
|
||||
|
||||
case 'ADD_RETENTION_POLICY': {
|
||||
const {database} = action.payload
|
||||
const databases = state.databases.map(db =>
|
||||
db.links.self === database.links.self ?
|
||||
{...database, retentionPolicies: [{...NEW_EMPTY_RP}, ...database.retentionPolicies]}
|
||||
: db
|
||||
const databases = state.databases.map(
|
||||
db =>
|
||||
(db.links.self === database.links.self
|
||||
? {
|
||||
...database,
|
||||
retentionPolicies: [
|
||||
{...NEW_EMPTY_RP},
|
||||
...database.retentionPolicies,
|
||||
],
|
||||
}
|
||||
: db)
|
||||
)
|
||||
|
||||
return {...state, databases}
|
||||
|
@ -81,7 +79,9 @@ export default function admin(state = initialState, action) {
|
|||
case 'SYNC_USER': {
|
||||
const {staleUser, syncedUser} = action.payload
|
||||
const newState = {
|
||||
users: state.users.map(u => u.links.self === staleUser.links.self ? {...syncedUser} : u),
|
||||
users: state.users.map(
|
||||
u => (u.links.self === staleUser.links.self ? {...syncedUser} : u)
|
||||
),
|
||||
}
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
@ -89,7 +89,9 @@ export default function admin(state = initialState, action) {
|
|||
case 'SYNC_ROLE': {
|
||||
const {staleRole, syncedRole} = action.payload
|
||||
const newState = {
|
||||
roles: state.roles.map(r => r.links.self === staleRole.links.self ? {...syncedRole} : r),
|
||||
roles: state.roles.map(
|
||||
r => (r.links.self === staleRole.links.self ? {...syncedRole} : r)
|
||||
),
|
||||
}
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
@ -97,7 +99,9 @@ export default function admin(state = initialState, action) {
|
|||
case 'SYNC_DATABASE': {
|
||||
const {stale, synced} = action.payload
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === stale.links.self ? {...synced} : db),
|
||||
databases: state.databases.map(
|
||||
db => (db.links.self === stale.links.self ? {...synced} : db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -106,10 +110,18 @@ export default function admin(state = initialState, action) {
|
|||
case 'SYNC_RETENTION_POLICY': {
|
||||
const {database, stale, synced} = action.payload
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === database.links.self ? {
|
||||
...db,
|
||||
retentionPolicies: db.retentionPolicies.map(rp => rp.links.self === stale.links.self ? {...synced} : rp),
|
||||
} : db),
|
||||
databases: state.databases.map(
|
||||
db =>
|
||||
(db.links.self === database.links.self
|
||||
? {
|
||||
...db,
|
||||
retentionPolicies: db.retentionPolicies.map(
|
||||
rp =>
|
||||
(rp.links.self === stale.links.self ? {...synced} : rp)
|
||||
),
|
||||
}
|
||||
: db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -118,7 +130,9 @@ export default function admin(state = initialState, action) {
|
|||
case 'EDIT_USER': {
|
||||
const {user, updates} = action.payload
|
||||
const newState = {
|
||||
users: state.users.map(u => u.links.self === user.links.self ? {...u, ...updates} : u),
|
||||
users: state.users.map(
|
||||
u => (u.links.self === user.links.self ? {...u, ...updates} : u)
|
||||
),
|
||||
}
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
@ -126,7 +140,9 @@ export default function admin(state = initialState, action) {
|
|||
case 'EDIT_ROLE': {
|
||||
const {role, updates} = action.payload
|
||||
const newState = {
|
||||
roles: state.roles.map(r => r.links.self === role.links.self ? {...r, ...updates} : r),
|
||||
roles: state.roles.map(
|
||||
r => (r.links.self === role.links.self ? {...r, ...updates} : r)
|
||||
),
|
||||
}
|
||||
return {...state, ...newState}
|
||||
}
|
||||
|
@ -134,7 +150,10 @@ export default function admin(state = initialState, action) {
|
|||
case 'EDIT_DATABASE': {
|
||||
const {database, updates} = action.payload
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === database.links.self ? {...db, ...updates} : db),
|
||||
databases: state.databases.map(
|
||||
db =>
|
||||
(db.links.self === database.links.self ? {...db, ...updates} : db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -144,10 +163,20 @@ export default function admin(state = initialState, action) {
|
|||
const {database, retentionPolicy, updates} = action.payload
|
||||
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === database.links.self ? {
|
||||
...db,
|
||||
retentionPolicies: db.retentionPolicies.map(rp => rp.links.self === retentionPolicy.links.self ? {...rp, ...updates} : rp),
|
||||
} : db),
|
||||
databases: state.databases.map(
|
||||
db =>
|
||||
(db.links.self === database.links.self
|
||||
? {
|
||||
...db,
|
||||
retentionPolicies: db.retentionPolicies.map(
|
||||
rp =>
|
||||
(rp.links.self === retentionPolicy.links.self
|
||||
? {...rp, ...updates}
|
||||
: rp)
|
||||
),
|
||||
}
|
||||
: db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -174,7 +203,9 @@ export default function admin(state = initialState, action) {
|
|||
case 'REMOVE_DATABASE': {
|
||||
const {database} = action.payload
|
||||
const newState = {
|
||||
databases: state.databases.filter(db => db.links.self !== database.links.self),
|
||||
databases: state.databases.filter(
|
||||
db => db.links.self !== database.links.self
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -183,11 +214,17 @@ export default function admin(state = initialState, action) {
|
|||
case 'REMOVE_RETENTION_POLICY': {
|
||||
const {database, retentionPolicy} = action.payload
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === database.links.self ? {
|
||||
...db,
|
||||
retentionPolicies: db.retentionPolicies.filter(rp => rp.links.self !== retentionPolicy.links.self),
|
||||
}
|
||||
: db),
|
||||
databases: state.databases.map(
|
||||
db =>
|
||||
(db.links.self === database.links.self
|
||||
? {
|
||||
...db,
|
||||
retentionPolicies: db.retentionPolicies.filter(
|
||||
rp => rp.links.self !== retentionPolicy.links.self
|
||||
),
|
||||
}
|
||||
: db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -196,7 +233,12 @@ export default function admin(state = initialState, action) {
|
|||
case 'ADD_DATABASE_DELETE_CODE': {
|
||||
const {database} = action.payload
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === database.links.self ? {...db, deleteCode: ''} : db),
|
||||
databases: state.databases.map(
|
||||
db =>
|
||||
(db.links.self === database.links.self
|
||||
? {...db, deleteCode: ''}
|
||||
: db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -207,7 +249,9 @@ export default function admin(state = initialState, action) {
|
|||
delete database.deleteCode
|
||||
|
||||
const newState = {
|
||||
databases: state.databases.map(db => db.links.self === database.links.self ? {...database} : db),
|
||||
databases: state.databases.map(
|
||||
db => (db.links.self === database.links.self ? {...database} : db)
|
||||
),
|
||||
}
|
||||
|
||||
return {...state, ...newState}
|
||||
|
@ -242,7 +286,7 @@ export default function admin(state = initialState, action) {
|
|||
case 'KILL_QUERY': {
|
||||
const {queryID} = action.payload
|
||||
const nextState = {
|
||||
queries: reject(state.queries, (q) => +q.id === +queryID),
|
||||
queries: reject(state.queries, q => +q.id === +queryID),
|
||||
}
|
||||
|
||||
return {...state, ...nextState}
|
||||
|
|
|
@ -4,13 +4,15 @@ import {Link} from 'react-router'
|
|||
|
||||
const AlertsTable = React.createClass({
|
||||
propTypes: {
|
||||
alerts: PropTypes.arrayOf(PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
time: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
host: PropTypes.string,
|
||||
level: PropTypes.string,
|
||||
})),
|
||||
alerts: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
name: PropTypes.string,
|
||||
time: PropTypes.string,
|
||||
value: PropTypes.string,
|
||||
host: PropTypes.string,
|
||||
level: PropTypes.string,
|
||||
})
|
||||
),
|
||||
source: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
|
@ -32,14 +34,16 @@ const AlertsTable = React.createClass({
|
|||
|
||||
filterAlerts(searchTerm, newAlerts) {
|
||||
const alerts = newAlerts || this.props.alerts
|
||||
const filteredAlerts = alerts.filter((h) => {
|
||||
const filteredAlerts = alerts.filter(h => {
|
||||
if (h.host === null || h.name === null || h.level === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return h.name.toLowerCase().search((searchTerm).toLowerCase()) !== -1 ||
|
||||
h.host.toLowerCase().search((searchTerm).toLowerCase()) !== -1 ||
|
||||
h.level.toLowerCase().search((searchTerm).toLowerCase()) !== -1
|
||||
return (
|
||||
h.name.toLowerCase().search(searchTerm.toLowerCase()) !== -1 ||
|
||||
h.host.toLowerCase().search(searchTerm.toLowerCase()) !== -1 ||
|
||||
h.level.toLowerCase().search(searchTerm.toLowerCase()) !== -1
|
||||
)
|
||||
})
|
||||
this.setState({searchTerm, filteredAlerts})
|
||||
},
|
||||
|
@ -47,7 +51,9 @@ const AlertsTable = React.createClass({
|
|||
changeSort(key) {
|
||||
// if we're using the key, reverse order; otherwise, set it with ascending
|
||||
if (this.state.sortKey === key) {
|
||||
const reverseDirection = (this.state.sortDirection === 'asc' ? 'desc' : 'asc')
|
||||
const reverseDirection = this.state.sortDirection === 'asc'
|
||||
? 'desc'
|
||||
: 'asc'
|
||||
this.setState({sortDirection: reverseDirection})
|
||||
} else {
|
||||
this.setState({sortKey: key, sortDirection: 'asc'})
|
||||
|
@ -57,9 +63,9 @@ const AlertsTable = React.createClass({
|
|||
sort(alerts, key, direction) {
|
||||
switch (direction) {
|
||||
case 'asc':
|
||||
return _.sortBy(alerts, (e) => e[key])
|
||||
return _.sortBy(alerts, e => e[key])
|
||||
case 'desc':
|
||||
return _.sortBy(alerts, (e) => e[key]).reverse()
|
||||
return _.sortBy(alerts, e => e[key]).reverse()
|
||||
default:
|
||||
return alerts
|
||||
}
|
||||
|
@ -67,42 +73,75 @@ const AlertsTable = React.createClass({
|
|||
|
||||
render() {
|
||||
const {id} = this.props.source
|
||||
const alerts = this.sort(this.state.filteredAlerts, this.state.sortKey, this.state.sortDirection)
|
||||
const alerts = this.sort(
|
||||
this.state.filteredAlerts,
|
||||
this.state.sortKey,
|
||||
this.state.sortDirection
|
||||
)
|
||||
return (
|
||||
<div className="panel panel-minimal">
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<h2 className="panel-title">{this.props.alerts.length} Alerts</h2>
|
||||
<SearchBar onSearch={this.filterAlerts}/>
|
||||
<SearchBar onSearch={this.filterAlerts} />
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
<table className="table v-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onClick={() => this.changeSort('name')} className="sortable-header">Name</th>
|
||||
<th onClick={() => this.changeSort('level')} className="sortable-header">Level</th>
|
||||
<th onClick={() => this.changeSort('time')} className="sortable-header">Time</th>
|
||||
<th onClick={() => this.changeSort('host')} className="sortable-header">Host</th>
|
||||
<th onClick={() => this.changeSort('value')} className="sortable-header">Value</th>
|
||||
<th
|
||||
onClick={() => this.changeSort('name')}
|
||||
className="sortable-header"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.changeSort('level')}
|
||||
className="sortable-header"
|
||||
>
|
||||
Level
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.changeSort('time')}
|
||||
className="sortable-header"
|
||||
>
|
||||
Time
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.changeSort('host')}
|
||||
className="sortable-header"
|
||||
>
|
||||
Host
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.changeSort('value')}
|
||||
className="sortable-header"
|
||||
>
|
||||
Value
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
alerts.map(({name, level, time, host, value}) => {
|
||||
return (
|
||||
<tr key={`${name}-${level}-${time}-${host}-${value}`}>
|
||||
<td className="monotype">{name}</td>
|
||||
<td className={`monotype alert-level-${level.toLowerCase()}`}>{level}</td>
|
||||
<td className="monotype">{(new Date(Number(time)).toISOString())}</td>
|
||||
<td className="monotype">
|
||||
<Link to={`/sources/${id}/hosts/${host}`}>
|
||||
{host}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="monotype">{value}</td>
|
||||
</tr>
|
||||
)
|
||||
})
|
||||
}
|
||||
{alerts.map(({name, level, time, host, value}) => {
|
||||
return (
|
||||
<tr key={`${name}-${level}-${time}-${host}-${value}`}>
|
||||
<td className="monotype">{name}</td>
|
||||
<td
|
||||
className={`monotype alert-level-${level.toLowerCase()}`}
|
||||
>
|
||||
{level}
|
||||
</td>
|
||||
<td className="monotype">
|
||||
{new Date(Number(time)).toISOString()}
|
||||
</td>
|
||||
<td className="monotype">
|
||||
<Link to={`/sources/${id}/hosts/${host}`}>
|
||||
{host}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="monotype">{value}</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -146,7 +185,7 @@ const SearchBar = React.createClass({
|
|||
value={this.state.searchTerm}
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
<span className="icon search" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -55,7 +55,10 @@ class AlertsApp extends Component {
|
|||
}
|
||||
|
||||
fetchAlerts() {
|
||||
getAlerts(this.props.source.links.proxy, this.state.timeRange).then((resp) => {
|
||||
getAlerts(
|
||||
this.props.source.links.proxy,
|
||||
this.state.timeRange
|
||||
).then(resp => {
|
||||
const results = []
|
||||
|
||||
const alertSeries = _.get(resp, ['data', 'results', '0', 'series'], [])
|
||||
|
@ -64,13 +67,19 @@ class AlertsApp extends Component {
|
|||
return
|
||||
}
|
||||
|
||||
const timeIndex = alertSeries[0].columns.findIndex((col) => col === 'time')
|
||||
const hostIndex = alertSeries[0].columns.findIndex((col) => col === 'host')
|
||||
const valueIndex = alertSeries[0].columns.findIndex((col) => col === 'value')
|
||||
const levelIndex = alertSeries[0].columns.findIndex((col) => col === 'level')
|
||||
const nameIndex = alertSeries[0].columns.findIndex((col) => col === 'alertName')
|
||||
const timeIndex = alertSeries[0].columns.findIndex(col => col === 'time')
|
||||
const hostIndex = alertSeries[0].columns.findIndex(col => col === 'host')
|
||||
const valueIndex = alertSeries[0].columns.findIndex(
|
||||
col => col === 'value'
|
||||
)
|
||||
const levelIndex = alertSeries[0].columns.findIndex(
|
||||
col => col === 'level'
|
||||
)
|
||||
const nameIndex = alertSeries[0].columns.findIndex(
|
||||
col => col === 'alertName'
|
||||
)
|
||||
|
||||
alertSeries[0].values.forEach((s) => {
|
||||
alertSeries[0].values.forEach(s => {
|
||||
results.push({
|
||||
time: `${s[timeIndex]}`,
|
||||
host: s[hostIndex],
|
||||
|
@ -86,13 +95,11 @@ class AlertsApp extends Component {
|
|||
renderSubComponents() {
|
||||
let component
|
||||
if (this.state.loading) {
|
||||
component = (<p>Loading...</p>)
|
||||
component = <p>Loading...</p>
|
||||
} else {
|
||||
const {source} = this.props
|
||||
if (this.state.hasKapacitor) {
|
||||
component = (
|
||||
<AlertsTable source={source} alerts={this.state.alerts} />
|
||||
)
|
||||
component = <AlertsTable source={source} alerts={this.state.alerts} />
|
||||
} else {
|
||||
component = <NoKapacitorError source={source} />
|
||||
}
|
||||
|
@ -155,11 +162,7 @@ class AlertsApp extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
AlertsApp.propTypes = {
|
||||
source: shape({
|
||||
|
|
|
@ -5,7 +5,7 @@ import {UserAuthWrapper} from 'redux-auth-wrapper'
|
|||
export const UserIsAuthenticated = UserAuthWrapper({
|
||||
authSelector: ({auth}) => ({auth}),
|
||||
authenticatingSelector: ({auth: {isMeLoading}}) => isMeLoading,
|
||||
LoadingComponent: (() => <div className="page-spinner" />),
|
||||
LoadingComponent: () => <div className="page-spinner" />,
|
||||
redirectAction: replace,
|
||||
wrapperDisplayName: 'UserIsAuthenticated',
|
||||
predicate: ({auth: {me, isMeLoading}}) => !isMeLoading && me !== null,
|
||||
|
@ -14,7 +14,7 @@ export const UserIsAuthenticated = UserAuthWrapper({
|
|||
export const UserIsNotAuthenticated = UserAuthWrapper({
|
||||
authSelector: ({auth}) => ({auth}),
|
||||
authenticatingSelector: ({auth: {isMeLoading}}) => isMeLoading,
|
||||
LoadingComponent: (() => <div className="page-spinner" />),
|
||||
LoadingComponent: () => <div className="page-spinner" />,
|
||||
redirectAction: replace,
|
||||
wrapperDisplayName: 'UserIsNotAuthenticated',
|
||||
predicate: ({auth: {me, isMeLoading}}) => !isMeLoading && me === null,
|
||||
|
|
|
@ -5,7 +5,7 @@ import Notifications from 'shared/components/Notifications'
|
|||
|
||||
const Login = ({authData: {auth}}) => {
|
||||
if (auth.isAuthLoading) {
|
||||
return <div className="page-spinner"></div>
|
||||
return <div className="page-spinner" />
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -13,29 +13,27 @@ const Login = ({authData: {auth}}) => {
|
|||
<Notifications />
|
||||
<div className="auth-page">
|
||||
<div className="auth-box">
|
||||
<div className="auth-logo"></div>
|
||||
<div className="auth-logo" />
|
||||
<h1 className="auth-text-logo">Chronograf</h1>
|
||||
<p><strong>{VERSION}</strong> / Time-Series Data Visualization</p>
|
||||
{auth.links && auth.links.map(({name, login, label}) => (
|
||||
<a key={name} className="btn btn-primary" href={login}>
|
||||
<span className={`icon ${name}`}></span>
|
||||
Login with {label}
|
||||
</a>
|
||||
))}
|
||||
{auth.links &&
|
||||
auth.links.map(({name, login, label}) => (
|
||||
<a key={name} className="btn btn-primary" href={login}>
|
||||
<span className={`icon ${name}`} />
|
||||
Login with {label}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
<p className="auth-credits">Made by <span className="icon cubo-uniform"></span>InfluxData</p>
|
||||
<div className="auth-image"></div>
|
||||
<p className="auth-credits">
|
||||
Made by <span className="icon cubo-uniform" />InfluxData
|
||||
</p>
|
||||
<div className="auth-image" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
array,
|
||||
bool,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {array, bool, shape, string} = PropTypes
|
||||
|
||||
Login.propTypes = {
|
||||
authData: shape({
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
import Login from './Login'
|
||||
import {UserIsAuthenticated, Authenticated, UserIsNotAuthenticated} from './Authenticated'
|
||||
import {
|
||||
UserIsAuthenticated,
|
||||
Authenticated,
|
||||
UserIsNotAuthenticated,
|
||||
} from './Authenticated'
|
||||
export {Login, UserIsAuthenticated, Authenticated, UserIsNotAuthenticated}
|
||||
|
|
|
@ -19,57 +19,66 @@ const DashboardHeader = ({
|
|||
source,
|
||||
onAddCell,
|
||||
onEditDashboard,
|
||||
}) => isHidden ? null : (
|
||||
<div className="page-header full-width">
|
||||
<div className="page-header__container">
|
||||
<div className="page-header__left">
|
||||
{buttonText &&
|
||||
<div className="dropdown page-header-dropdown">
|
||||
<button className="dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
<span className="button-text">{buttonText}</span>
|
||||
<span className="caret"></span>
|
||||
</button>
|
||||
<ul className="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
{children}
|
||||
</ul>
|
||||
</div>}
|
||||
{headerText}
|
||||
</div>
|
||||
<div className="page-header__right">
|
||||
<GraphTips />
|
||||
<SourceIndicator sourceName={source.name} />
|
||||
{
|
||||
dashboard ?
|
||||
<button className="btn btn-primary btn-sm" onClick={onAddCell}>
|
||||
<span className="icon plus" />
|
||||
Add Cell
|
||||
</button> : null
|
||||
}
|
||||
{
|
||||
dashboard ?
|
||||
<button className="btn btn-info btn-sm" onClick={onEditDashboard}>
|
||||
<span className="icon pencil" />
|
||||
Rename
|
||||
</button> : null
|
||||
}
|
||||
<AutoRefreshDropdown onChoose={handleChooseAutoRefresh} selected={autoRefresh} iconName="refresh" />
|
||||
<TimeRangeDropdown onChooseTimeRange={handleChooseTimeRange} selected={timeRange} />
|
||||
<div className="btn btn-info btn-sm" onClick={handleClickPresentationButton}>
|
||||
<span className="icon expand-a" style={{margin: 0}}></span>
|
||||
}) =>
|
||||
(isHidden
|
||||
? null
|
||||
: <div className="page-header full-width">
|
||||
<div className="page-header__container">
|
||||
<div className="page-header__left">
|
||||
{buttonText &&
|
||||
<div className="dropdown page-header-dropdown">
|
||||
<button
|
||||
className="dropdown-toggle"
|
||||
type="button"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<span className="button-text">{buttonText}</span>
|
||||
<span className="caret" />
|
||||
</button>
|
||||
<ul className="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
{children}
|
||||
</ul>
|
||||
</div>}
|
||||
{headerText}
|
||||
</div>
|
||||
<div className="page-header__right">
|
||||
<GraphTips />
|
||||
<SourceIndicator sourceName={source.name} />
|
||||
{dashboard
|
||||
? <button className="btn btn-primary btn-sm" onClick={onAddCell}>
|
||||
<span className="icon plus" />
|
||||
Add Cell
|
||||
</button>
|
||||
: null}
|
||||
{dashboard
|
||||
? <button
|
||||
className="btn btn-info btn-sm"
|
||||
onClick={onEditDashboard}
|
||||
>
|
||||
<span className="icon pencil" />
|
||||
Rename
|
||||
</button>
|
||||
: null}
|
||||
<AutoRefreshDropdown
|
||||
onChoose={handleChooseAutoRefresh}
|
||||
selected={autoRefresh}
|
||||
iconName="refresh"
|
||||
/>
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={handleChooseTimeRange}
|
||||
selected={timeRange}
|
||||
/>
|
||||
<div
|
||||
className="btn btn-info btn-sm"
|
||||
onClick={handleClickPresentationButton}
|
||||
>
|
||||
<span className="icon expand-a" style={{margin: 0}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
</div>)
|
||||
|
||||
const {
|
||||
array,
|
||||
bool,
|
||||
func,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {array, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
DashboardHeader.propTypes = {
|
||||
sourceID: string,
|
||||
|
|
|
@ -27,7 +27,7 @@ class DashboardEditHeader extends Component {
|
|||
autoFocus={true}
|
||||
value={name}
|
||||
placeholder="Name this Dashboard"
|
||||
onChange={(e) => this.handleChange(e.target.value)}
|
||||
onChange={e => this.handleChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<ConfirmButtons item={name} onConfirm={onSave} onCancel={onCancel} />
|
||||
|
@ -37,10 +37,7 @@ class DashboardEditHeader extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
shape,
|
||||
func,
|
||||
} = PropTypes
|
||||
const {shape, func} = PropTypes
|
||||
|
||||
DashboardEditHeader.propTypes = {
|
||||
dashboard: shape({}),
|
||||
|
|
|
@ -5,7 +5,7 @@ import ConfirmButtons from 'shared/components/ConfirmButtons'
|
|||
|
||||
import graphTypes from 'hson!shared/data/graphTypes.hson'
|
||||
|
||||
const OverlayControls = (props) => {
|
||||
const OverlayControls = props => {
|
||||
const {onCancel, onSave, selectedGraphType, onSelectGraphType} = props
|
||||
return (
|
||||
<div className="overlay-controls">
|
||||
|
@ -13,15 +13,17 @@ const OverlayControls = (props) => {
|
|||
<div className="overlay-controls--right">
|
||||
<p>Visualization Type:</p>
|
||||
<ul className="toggle toggle-sm">
|
||||
{graphTypes.map(graphType =>
|
||||
{graphTypes.map(graphType => (
|
||||
<li
|
||||
key={graphType.type}
|
||||
className={classnames('toggle-btn', {active: graphType.type === selectedGraphType})}
|
||||
className={classnames('toggle-btn', {
|
||||
active: graphType.type === selectedGraphType,
|
||||
})}
|
||||
onClick={() => onSelectGraphType(graphType.type)}
|
||||
>
|
||||
{graphType.menuOption}
|
||||
</li>
|
||||
)}
|
||||
))}
|
||||
</ul>
|
||||
<ConfirmButtons onCancel={onCancel} onConfirm={onSave} />
|
||||
</div>
|
||||
|
@ -29,10 +31,7 @@ const OverlayControls = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, string} = PropTypes
|
||||
|
||||
OverlayControls.propTypes = {
|
||||
onCancel: func.isRequired,
|
||||
|
|
|
@ -34,9 +34,10 @@ const TemplateVariableTable = ({
|
|||
</div>
|
||||
</div>
|
||||
: <div className="generic-empty-state">
|
||||
<h4 style={{margin: '60px 0'}} className="no-user-select">You have no Template Variables, why not create one?</h4>
|
||||
</div>
|
||||
}
|
||||
<h4 style={{margin: '60px 0'}} className="no-user-select">
|
||||
You have no Template Variables, why not create one?
|
||||
</h4>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ export function updateRawQuery(queryID, text) {
|
|||
}
|
||||
}
|
||||
|
||||
export const updateQueryConfig = (config) => ({
|
||||
export const updateQueryConfig = config => ({
|
||||
type: 'UPDATE_QUERY_CONFIG',
|
||||
payload: {
|
||||
config,
|
||||
|
@ -149,7 +149,7 @@ export const editQueryStatus = (queryID, status) => ({
|
|||
})
|
||||
|
||||
// Async actions
|
||||
export const editRawTextAsync = (url, id, text) => async (dispatch) => {
|
||||
export const editRawTextAsync = (url, id, text) => async dispatch => {
|
||||
try {
|
||||
const {data} = await getQueryConfig(url, [{query: text, id}])
|
||||
const config = data.queries.find(q => q.id === id)
|
||||
|
|
|
@ -29,21 +29,23 @@ const DatabaseList = React.createClass({
|
|||
componentDidMount() {
|
||||
const {source} = this.context
|
||||
const proxy = source.links.proxy
|
||||
showDatabases(proxy).then((resp) => {
|
||||
showDatabases(proxy).then(resp => {
|
||||
const {errors, databases} = showDatabasesParser(resp.data)
|
||||
if (errors.length) {
|
||||
// do something
|
||||
}
|
||||
|
||||
const namespaces = []
|
||||
showRetentionPolicies(proxy, databases).then((res) => {
|
||||
showRetentionPolicies(proxy, databases).then(res => {
|
||||
res.data.results.forEach((result, index) => {
|
||||
const {errors: errs, retentionPolicies} = showRetentionPoliciesParser(result)
|
||||
const {errors: errs, retentionPolicies} = showRetentionPoliciesParser(
|
||||
result
|
||||
)
|
||||
if (errs.length) {
|
||||
// do something
|
||||
}
|
||||
|
||||
retentionPolicies.forEach((rp) => {
|
||||
retentionPolicies.forEach(rp => {
|
||||
namespaces.push({
|
||||
database: databases[index],
|
||||
retentionPolicy: rp.name,
|
||||
|
@ -63,12 +65,20 @@ const DatabaseList = React.createClass({
|
|||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">Databases</div>
|
||||
<div className="query-builder--list">
|
||||
{this.state.namespaces.map((namespace) => {
|
||||
{this.state.namespaces.map(namespace => {
|
||||
const {database, retentionPolicy} = namespace
|
||||
const isActive = database === query.database && retentionPolicy === query.retentionPolicy
|
||||
const isActive =
|
||||
database === query.database &&
|
||||
retentionPolicy === query.retentionPolicy
|
||||
|
||||
return (
|
||||
<div className={classNames('query-builder--list-item', {active: isActive})} key={`${database}..${retentionPolicy}`} onClick={_.wrap(namespace, onChooseNamespace)}>
|
||||
<div
|
||||
className={classNames('query-builder--list-item', {
|
||||
active: isActive,
|
||||
})}
|
||||
key={`${database}..${retentionPolicy}`}
|
||||
onClick={_.wrap(namespace, onChooseNamespace)}
|
||||
>
|
||||
{database}.{retentionPolicy}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -51,12 +51,20 @@ const FieldList = React.createClass({
|
|||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {database, measurement, retentionPolicy} = this.props.query
|
||||
const {database: prevDB, measurement: prevMeas, retentionPolicy: prevRP} = prevProps.query
|
||||
const {
|
||||
database: prevDB,
|
||||
measurement: prevMeas,
|
||||
retentionPolicy: prevRP,
|
||||
} = prevProps.query
|
||||
if (!database || !measurement) {
|
||||
return
|
||||
}
|
||||
|
||||
if (database === prevDB && measurement === prevMeas && retentionPolicy === prevRP) {
|
||||
if (
|
||||
database === prevDB &&
|
||||
measurement === prevMeas &&
|
||||
retentionPolicy === prevRP
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -69,16 +77,20 @@ const FieldList = React.createClass({
|
|||
|
||||
render() {
|
||||
const {query} = this.props
|
||||
const hasAggregates = query.fields.some((f) => f.funcs && f.funcs.length)
|
||||
const hasAggregates = query.fields.some(f => f.funcs && f.funcs.length)
|
||||
const hasGroupByTime = query.groupBy.time
|
||||
|
||||
return (
|
||||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">
|
||||
<span>Fields</span>
|
||||
{hasAggregates ?
|
||||
<GroupByTimeDropdown isOpen={!hasGroupByTime} selected={query.groupBy.time} onChooseGroupByTime={this.handleGroupByTime} />
|
||||
: null}
|
||||
{hasAggregates
|
||||
? <GroupByTimeDropdown
|
||||
isOpen={!hasGroupByTime}
|
||||
selected={query.groupBy.time}
|
||||
onChooseGroupByTime={this.handleGroupByTime}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
{this.renderList()}
|
||||
</div>
|
||||
|
@ -97,8 +109,10 @@ const FieldList = React.createClass({
|
|||
|
||||
return (
|
||||
<div className="query-builder--list">
|
||||
{this.state.fields.map((fieldFunc) => {
|
||||
const selectedField = this.props.query.fields.find((f) => f.field === fieldFunc.field)
|
||||
{this.state.fields.map(fieldFunc => {
|
||||
const selectedField = this.props.query.fields.find(
|
||||
f => f.field === fieldFunc.field
|
||||
)
|
||||
return (
|
||||
<FieldListItem
|
||||
key={fieldFunc.field}
|
||||
|
@ -119,14 +133,19 @@ const FieldList = React.createClass({
|
|||
const {source} = this.context
|
||||
const proxySource = source.links.proxy
|
||||
|
||||
showFieldKeys(proxySource, database, measurement, retentionPolicy).then((resp) => {
|
||||
showFieldKeys(
|
||||
proxySource,
|
||||
database,
|
||||
measurement,
|
||||
retentionPolicy
|
||||
).then(resp => {
|
||||
const {errors, fieldSets} = showFieldKeysParser(resp.data)
|
||||
if (errors.length) {
|
||||
// TODO: do something
|
||||
}
|
||||
|
||||
this.setState({
|
||||
fields: fieldSets[measurement].map((f) => {
|
||||
fields: fieldSets[measurement].map(f => {
|
||||
return {field: f, funcs: []}
|
||||
}),
|
||||
})
|
||||
|
|
|
@ -34,21 +34,33 @@ const FieldListItem = React.createClass({
|
|||
render() {
|
||||
const {isKapacitorRule, fieldFunc, isSelected} = this.props
|
||||
const {field: fieldText} = fieldFunc
|
||||
const items = INFLUXQL_FUNCTIONS.map((text) => {
|
||||
const items = INFLUXQL_FUNCTIONS.map(text => {
|
||||
return {text}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={classNames('query-builder--list-item', {active: isSelected})} key={fieldFunc} onClick={_.wrap(fieldFunc, this.handleToggleField)}>
|
||||
<div
|
||||
className={classNames('query-builder--list-item', {active: isSelected})}
|
||||
key={fieldFunc}
|
||||
onClick={_.wrap(fieldFunc, this.handleToggleField)}
|
||||
>
|
||||
<span>
|
||||
<div className="query-builder--checkbox"></div>
|
||||
<div className="query-builder--checkbox" />
|
||||
{fieldText}
|
||||
</span>
|
||||
{
|
||||
isKapacitorRule ?
|
||||
<Dropdown items={items} onChoose={this.handleApplyFunctions} selected={fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'} /> :
|
||||
<MultiSelectDropdown items={INFLUXQL_FUNCTIONS} onApply={this.handleApplyFunctions} selectedItems={fieldFunc.funcs || []} />
|
||||
}
|
||||
{isKapacitorRule
|
||||
? <Dropdown
|
||||
items={items}
|
||||
onChoose={this.handleApplyFunctions}
|
||||
selected={
|
||||
fieldFunc.funcs.length ? fieldFunc.funcs[0] : 'Function'
|
||||
}
|
||||
/>
|
||||
: <MultiSelectDropdown
|
||||
items={INFLUXQL_FUNCTIONS}
|
||||
onApply={this.handleApplyFunctions}
|
||||
selectedItems={fieldFunc.funcs || []}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -16,15 +16,26 @@ const GroupByTimeDropdown = React.createClass({
|
|||
const {isOpen, selected, onChooseGroupByTime} = this.props
|
||||
|
||||
return (
|
||||
<div className={classNames('dropdown group-by-time-dropdown', {open: isOpen})}>
|
||||
<div className="btn btn-sm btn-info dropdown-toggle" data-toggle="dropdown">
|
||||
<div
|
||||
className={classNames('dropdown group-by-time-dropdown', {
|
||||
open: isOpen,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className="btn btn-sm btn-info dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<span>Group by {selected || 'time'}</span>
|
||||
<span className="caret"></span>
|
||||
<span className="caret" />
|
||||
</div>
|
||||
<ul className="dropdown-menu">
|
||||
{groupByTimeOptions.map((groupBy) => {
|
||||
{groupByTimeOptions.map(groupBy => {
|
||||
return (
|
||||
<li className="dropdown-item" key={groupBy.menuOption}onClick={() => onChooseGroupByTime(groupBy)}>
|
||||
<li
|
||||
className="dropdown-item"
|
||||
key={groupBy.menuOption}
|
||||
onClick={() => onChooseGroupByTime(groupBy)}
|
||||
>
|
||||
<a href="#">
|
||||
{groupBy.menuOption}
|
||||
</a>
|
||||
|
|
|
@ -74,12 +74,20 @@ const MeasurementList = React.createClass({
|
|||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">
|
||||
<span>Measurements</span>
|
||||
{this.props.query.database ?
|
||||
<div className="query-builder--filter">
|
||||
<input className="form-control input-sm" ref="filterText" placeholder="Filter" type="text" value={this.state.filterText} onChange={this.handleFilterText} onKeyUp={this.handleEscape} />
|
||||
<span className="icon search"></span>
|
||||
</div>
|
||||
: null }
|
||||
{this.props.query.database
|
||||
? <div className="query-builder--filter">
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
ref="filterText"
|
||||
placeholder="Filter"
|
||||
type="text"
|
||||
value={this.state.filterText}
|
||||
onChange={this.handleFilterText}
|
||||
onKeyUp={this.handleEscape}
|
||||
/>
|
||||
<span className="icon search" />
|
||||
</div>
|
||||
: null}
|
||||
</div>
|
||||
{this.renderList()}
|
||||
</div>
|
||||
|
@ -95,14 +103,24 @@ const MeasurementList = React.createClass({
|
|||
)
|
||||
}
|
||||
|
||||
const measurements = this.state.measurements.filter((m) => m.match(this.state.filterText))
|
||||
const measurements = this.state.measurements.filter(m =>
|
||||
m.match(this.state.filterText)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="query-builder--list">
|
||||
{measurements.map((measurement) => {
|
||||
{measurements.map(measurement => {
|
||||
const isActive = measurement === this.props.query.measurement
|
||||
return (
|
||||
<div className={classNames('query-builder--list-item', {active: isActive})} key={measurement} onClick={_.wrap(measurement, this.props.onChooseMeasurement)}>{measurement}</div>
|
||||
<div
|
||||
className={classNames('query-builder--list-item', {
|
||||
active: isActive,
|
||||
})}
|
||||
key={measurement}
|
||||
onClick={_.wrap(measurement, this.props.onChooseMeasurement)}
|
||||
>
|
||||
{measurement}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
@ -112,7 +130,7 @@ const MeasurementList = React.createClass({
|
|||
_getMeasurements() {
|
||||
const {source} = this.context
|
||||
const proxy = source.links.proxy
|
||||
showMeasurements(proxy, this.props.query.database).then((resp) => {
|
||||
showMeasurements(proxy, this.props.query.database).then(resp => {
|
||||
const {errors, measurementSets} = showMeasurementsParser(resp.data)
|
||||
if (errors.length) {
|
||||
// TODO: display errors in the UI.
|
||||
|
@ -124,7 +142,6 @@ const MeasurementList = React.createClass({
|
|||
})
|
||||
})
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
export default MeasurementList
|
||||
|
|
|
@ -84,13 +84,17 @@ const QueryBuilder = React.createClass({
|
|||
|
||||
return (
|
||||
<div className="query-maker--tab-contents">
|
||||
<QueryEditor query={q} config={query} onUpdate={this.handleEditRawText} templates={templates} />
|
||||
<QueryEditor
|
||||
query={q}
|
||||
config={query}
|
||||
onUpdate={this.handleEditRawText}
|
||||
templates={templates}
|
||||
/>
|
||||
{this.renderLists()}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
|
||||
renderLists() {
|
||||
const {query} = this.props
|
||||
|
||||
|
|
|
@ -198,7 +198,7 @@ class QueryEditor extends Component {
|
|||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onBlur={this.handleUpdate}
|
||||
ref={editor => this.editor = editor}
|
||||
ref={editor => (this.editor = editor)}
|
||||
value={value}
|
||||
placeholder="Enter a query or select database, measurement, and field below and have us build one for you..."
|
||||
autoComplete="off"
|
||||
|
|
|
@ -81,8 +81,14 @@ const QueryMaker = React.createClass({
|
|||
return (
|
||||
<div className="query-maker--empty">
|
||||
<h5>This Graph has no Queries</h5>
|
||||
<br/>
|
||||
<div className="btn btn-primary" role="button" onClick={this.handleAddQuery}>Add a Query</div>
|
||||
<br />
|
||||
<div
|
||||
className="btn btn-primary"
|
||||
role="button"
|
||||
onClick={this.handleAddQuery}
|
||||
>
|
||||
Add a Query
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -100,7 +106,13 @@ const QueryMaker = React.createClass({
|
|||
},
|
||||
|
||||
renderQueryTabList() {
|
||||
const {queries, activeQueryIndex, onDeleteQuery, timeRange, setActiveQueryIndex} = this.props
|
||||
const {
|
||||
queries,
|
||||
activeQueryIndex,
|
||||
onDeleteQuery,
|
||||
timeRange,
|
||||
setActiveQueryIndex,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="query-maker--tabs">
|
||||
|
@ -113,13 +125,20 @@ const QueryMaker = React.createClass({
|
|||
query={q}
|
||||
onSelect={setActiveQueryIndex}
|
||||
onDelete={onDeleteQuery}
|
||||
queryTabText={q.rawText || buildInfluxQLQuery(timeRange, q) || `Query ${i + 1}`}
|
||||
queryTabText={
|
||||
q.rawText ||
|
||||
buildInfluxQLQuery(timeRange, q) ||
|
||||
`Query ${i + 1}`
|
||||
}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
{this.props.children}
|
||||
<div className="query-maker--new btn btn-sm btn-primary" onClick={this.handleAddQuery}>
|
||||
<span className="icon plus"></span>
|
||||
<div
|
||||
className="query-maker--new btn btn-sm btn-primary"
|
||||
onClick={this.handleAddQuery}
|
||||
>
|
||||
<span className="icon plus" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -24,9 +24,14 @@ const QueryMakerTab = React.createClass({
|
|||
|
||||
render() {
|
||||
return (
|
||||
<div className={classNames('query-maker--tab', {active: this.props.isActive})} onClick={this.handleSelect}>
|
||||
<div
|
||||
className={classNames('query-maker--tab', {
|
||||
active: this.props.isActive,
|
||||
})}
|
||||
onClick={this.handleSelect}
|
||||
>
|
||||
<label>{this.props.queryTabText}</label>
|
||||
<span className="query-maker--delete" onClick={this.handleDelete}></span>
|
||||
<span className="query-maker--delete" onClick={this.handleDelete} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -41,21 +41,29 @@ const TagList = React.createClass({
|
|||
const {source} = this.context
|
||||
const sourceProxy = source.links.proxy
|
||||
|
||||
showTagKeys({source: sourceProxy, database, retentionPolicy, measurement}).then((resp) => {
|
||||
const {errors, tagKeys} = showTagKeysParser(resp.data)
|
||||
if (errors.length) {
|
||||
// do something
|
||||
}
|
||||
showTagKeys({source: sourceProxy, database, retentionPolicy, measurement})
|
||||
.then(resp => {
|
||||
const {errors, tagKeys} = showTagKeysParser(resp.data)
|
||||
if (errors.length) {
|
||||
// do something
|
||||
}
|
||||
|
||||
return showTagValues({source: sourceProxy, database, retentionPolicy, measurement, tagKeys})
|
||||
}).then((resp) => {
|
||||
const {errors: errs, tags} = showTagValuesParser(resp.data)
|
||||
if (errs.length) {
|
||||
// do something
|
||||
}
|
||||
return showTagValues({
|
||||
source: sourceProxy,
|
||||
database,
|
||||
retentionPolicy,
|
||||
measurement,
|
||||
tagKeys,
|
||||
})
|
||||
})
|
||||
.then(resp => {
|
||||
const {errors: errs, tags} = showTagValuesParser(resp.data)
|
||||
if (errs.length) {
|
||||
// do something
|
||||
}
|
||||
|
||||
this.setState({tags})
|
||||
})
|
||||
this.setState({tags})
|
||||
})
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -69,12 +77,20 @@ const TagList = React.createClass({
|
|||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {database, measurement, retentionPolicy} = this.props.query
|
||||
const {database: prevDB, measurement: prevMeas, retentionPolicy: prevRP} = prevProps.query
|
||||
const {
|
||||
database: prevDB,
|
||||
measurement: prevMeas,
|
||||
retentionPolicy: prevRP,
|
||||
} = prevProps.query
|
||||
if (!database || !measurement || !retentionPolicy) {
|
||||
return
|
||||
}
|
||||
|
||||
if (database === prevDB && measurement === prevMeas && retentionPolicy === prevRP) {
|
||||
if (
|
||||
database === prevDB &&
|
||||
measurement === prevMeas &&
|
||||
retentionPolicy === prevRP
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -93,13 +109,17 @@ const TagList = React.createClass({
|
|||
<div className="query-builder--column">
|
||||
<div className="query-builder--heading">
|
||||
<span>Tags</span>
|
||||
{(!query.database || !query.measurement || !query.retentionPolicy) ? null :
|
||||
<div className={cx('flip-toggle', {flipped: query.areTagsAccepted})} onClick={this.handleAcceptReject}>
|
||||
<div className="flip-toggle--container">
|
||||
<div className="flip-toggle--front">!=</div>
|
||||
<div className="flip-toggle--back">=</div>
|
||||
</div>
|
||||
</div>}
|
||||
{!query.database || !query.measurement || !query.retentionPolicy
|
||||
? null
|
||||
: <div
|
||||
className={cx('flip-toggle', {flipped: query.areTagsAccepted})}
|
||||
onClick={this.handleAcceptReject}
|
||||
>
|
||||
<div className="flip-toggle--container">
|
||||
<div className="flip-toggle--front">!=</div>
|
||||
<div className="flip-toggle--back">=</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
{this.renderList()}
|
||||
</div>
|
||||
|
@ -125,7 +145,9 @@ const TagList = React.createClass({
|
|||
tagKey={tagKey}
|
||||
tagValues={tagValues}
|
||||
selectedTagValues={this.props.query.tags[tagKey] || []}
|
||||
isUsingGroupBy={this.props.query.groupBy.tags.indexOf(tagKey) > -1}
|
||||
isUsingGroupBy={
|
||||
this.props.query.groupBy.tags.indexOf(tagKey) > -1
|
||||
}
|
||||
onChooseTag={this.props.onChooseTag}
|
||||
onGroupByTag={this.props.onGroupByTag}
|
||||
/>
|
||||
|
|
|
@ -52,20 +52,30 @@ const TagListItem = React.createClass({
|
|||
return <div>no tag values</div>
|
||||
}
|
||||
|
||||
const filtered = tagValues.filter((v) => v.match(this.state.filterText))
|
||||
const filtered = tagValues.filter(v => v.match(this.state.filterText))
|
||||
|
||||
return (
|
||||
<div className="query-builder--sub-list">
|
||||
<div className="query-builder--filter">
|
||||
<input className="form-control input-sm" ref="filterText" placeholder={`Filter within ${this.props.tagKey}`} type="text" value={this.state.filterText} onChange={this.handleFilterText} onKeyUp={this.handleEscape} />
|
||||
<span className="icon search"></span>
|
||||
<input
|
||||
className="form-control input-sm"
|
||||
ref="filterText"
|
||||
placeholder={`Filter within ${this.props.tagKey}`}
|
||||
type="text"
|
||||
value={this.state.filterText}
|
||||
onChange={this.handleFilterText}
|
||||
onKeyUp={this.handleEscape}
|
||||
/>
|
||||
<span className="icon search" />
|
||||
</div>
|
||||
{filtered.map((v) => {
|
||||
const cx = classNames('query-builder--list-item', {active: selectedTagValues.indexOf(v) > -1})
|
||||
{filtered.map(v => {
|
||||
const cx = classNames('query-builder--list-item', {
|
||||
active: selectedTagValues.indexOf(v) > -1,
|
||||
})
|
||||
return (
|
||||
<div className={cx} onClick={_.wrap(v, this.handleChoose)} key={v}>
|
||||
<span>
|
||||
<div className="query-builder--checkbox"></div>
|
||||
<div className="query-builder--checkbox" />
|
||||
{v}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -87,14 +97,21 @@ const TagListItem = React.createClass({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className={classNames('query-builder--list-item', {active: isOpen})} onClick={this.handleClickKey}>
|
||||
<div
|
||||
className={classNames('query-builder--list-item', {active: isOpen})}
|
||||
onClick={this.handleClickKey}
|
||||
>
|
||||
<span>
|
||||
<div className="query-builder--caret icon caret-right"></div>
|
||||
<div className="query-builder--caret icon caret-right" />
|
||||
{tagItemLabel}
|
||||
</span>
|
||||
<div
|
||||
className={classNames('btn btn-info btn-xs group-by-tag', {active: this.props.isUsingGroupBy})}
|
||||
onClick={this.handleGroupBy}>Group By {tagKey}
|
||||
className={classNames('btn btn-info btn-xs group-by-tag', {
|
||||
active: this.props.isUsingGroupBy,
|
||||
})}
|
||||
onClick={this.handleGroupBy}
|
||||
>
|
||||
Group By {tagKey}
|
||||
</div>
|
||||
</div>
|
||||
{isOpen ? this.renderTagValues() : null}
|
||||
|
|
|
@ -6,12 +6,7 @@ import TimeRangeDropdown from '../../shared/components/TimeRangeDropdown'
|
|||
import SourceIndicator from '../../shared/components/SourceIndicator'
|
||||
import GraphTips from '../../shared/components/GraphTips'
|
||||
|
||||
const {
|
||||
func,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, number, shape, string} = PropTypes
|
||||
|
||||
const Header = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -37,7 +32,11 @@ const Header = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
const {autoRefresh, actions: {handleChooseAutoRefresh}, timeRange} = this.props
|
||||
const {
|
||||
autoRefresh,
|
||||
actions: {handleChooseAutoRefresh},
|
||||
timeRange,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className="page-header full-width-no-scrollbar">
|
||||
|
@ -50,8 +49,15 @@ const Header = React.createClass({
|
|||
<div className="page-header__right">
|
||||
<GraphTips />
|
||||
<SourceIndicator sourceName={this.context.source.name} />
|
||||
<AutoRefreshDropdown onChoose={handleChooseAutoRefresh} selected={autoRefresh} iconName="refresh" />
|
||||
<TimeRangeDropdown onChooseTimeRange={this.handleChooseTimeRange} selected={timeRange} />
|
||||
<AutoRefreshDropdown
|
||||
onChoose={handleChooseAutoRefresh}
|
||||
selected={autoRefresh}
|
||||
iconName="refresh"
|
||||
/>
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={this.handleChooseTimeRange}
|
||||
selected={timeRange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,7 +21,10 @@ export default function queryConfigs(state = {}, action) {
|
|||
|
||||
case 'CHOOSE_NAMESPACE': {
|
||||
const {queryId, database, retentionPolicy} = action.payload
|
||||
const nextQueryConfig = chooseNamespace(state[queryId], {database, retentionPolicy})
|
||||
const nextQueryConfig = chooseNamespace(state[queryId], {
|
||||
database,
|
||||
retentionPolicy,
|
||||
})
|
||||
|
||||
return Object.assign({}, state, {
|
||||
[queryId]: Object.assign(nextQueryConfig, {rawText: null}),
|
||||
|
@ -33,7 +36,9 @@ export default function queryConfigs(state = {}, action) {
|
|||
const nextQueryConfig = chooseMeasurement(state[queryId], measurement)
|
||||
|
||||
return Object.assign({}, state, {
|
||||
[queryId]: Object.assign(nextQueryConfig, {rawText: state[queryId].rawText}),
|
||||
[queryId]: Object.assign(nextQueryConfig, {
|
||||
rawText: state[queryId].rawText,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -99,10 +104,12 @@ export default function queryConfigs(state = {}, action) {
|
|||
|
||||
case 'DELETE_QUERY': {
|
||||
const {queryID} = action.payload
|
||||
const nextState = update(state, {$apply: (configs) => {
|
||||
delete configs[queryID]
|
||||
return configs
|
||||
}})
|
||||
const nextState = update(state, {
|
||||
$apply: configs => {
|
||||
delete configs[queryID]
|
||||
return configs
|
||||
},
|
||||
})
|
||||
|
||||
return nextState
|
||||
}
|
||||
|
@ -110,7 +117,11 @@ export default function queryConfigs(state = {}, action) {
|
|||
case 'TOGGLE_FIELD': {
|
||||
const {isKapacitorRule} = action.meta
|
||||
const {queryId, fieldFunc} = action.payload
|
||||
const nextQueryConfig = toggleField(state[queryId], fieldFunc, isKapacitorRule)
|
||||
const nextQueryConfig = toggleField(
|
||||
state[queryId],
|
||||
fieldFunc,
|
||||
isKapacitorRule
|
||||
)
|
||||
|
||||
return Object.assign({}, state, {
|
||||
[queryId]: {...nextQueryConfig, rawText: null},
|
||||
|
|
|
@ -5,42 +5,43 @@ export default Dygraph
|
|||
/* eslint-disable */
|
||||
Dygraph.prototype.findClosestPoint = function(domX, domY) {
|
||||
if (Dygraph.VERSION !== '1.1.1') {
|
||||
console.error('Dygraph version changed from expected - re-copy findClosestPoint');
|
||||
console.error(
|
||||
'Dygraph version changed from expected - re-copy findClosestPoint'
|
||||
)
|
||||
}
|
||||
var minXDist = Infinity;
|
||||
var minYDist = Infinity;
|
||||
var xdist, ydist, dx, dy, point, closestPoint, closestSeries, closestRow;
|
||||
for ( var setIdx = this.layout_.points.length - 1 ; setIdx >= 0 ; --setIdx ) {
|
||||
var points = this.layout_.points[setIdx];
|
||||
var minXDist = Infinity
|
||||
var minYDist = Infinity
|
||||
var xdist, ydist, dx, dy, point, closestPoint, closestSeries, closestRow
|
||||
for (var setIdx = this.layout_.points.length - 1; setIdx >= 0; --setIdx) {
|
||||
var points = this.layout_.points[setIdx]
|
||||
for (var i = 0; i < points.length; ++i) {
|
||||
point = points[i];
|
||||
if (!Dygraph.isValidPoint(point)) continue;
|
||||
point = points[i]
|
||||
if (!Dygraph.isValidPoint(point)) continue
|
||||
|
||||
dx = point.canvasx - domX;
|
||||
dy = point.canvasy - domY;
|
||||
dx = point.canvasx - domX
|
||||
dy = point.canvasy - domY
|
||||
|
||||
xdist = dx * dx;
|
||||
ydist = dy * dy;
|
||||
xdist = dx * dx
|
||||
ydist = dy * dy
|
||||
|
||||
if (xdist < minXDist) {
|
||||
minXDist = xdist;
|
||||
minYDist = ydist;
|
||||
closestRow = point.idx;
|
||||
closestSeries = setIdx;
|
||||
minXDist = xdist
|
||||
minYDist = ydist
|
||||
closestRow = point.idx
|
||||
closestSeries = setIdx
|
||||
} else if (xdist === minXDist && ydist < minYDist) {
|
||||
minXDist = xdist;
|
||||
minYDist = ydist;
|
||||
closestRow = point.idx;
|
||||
closestSeries = setIdx;
|
||||
minXDist = xdist
|
||||
minYDist = ydist
|
||||
closestRow = point.idx
|
||||
closestSeries = setIdx
|
||||
}
|
||||
}
|
||||
}
|
||||
var name = this.layout_.setNames[closestSeries];
|
||||
var name = this.layout_.setNames[closestSeries]
|
||||
return {
|
||||
row: closestRow,
|
||||
seriesName: name,
|
||||
point: closestPoint
|
||||
};
|
||||
};
|
||||
point: closestPoint,
|
||||
}
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
|
|
|
@ -3,22 +3,18 @@ import shallowCompare from 'react-addons-shallow-compare'
|
|||
import {Link} from 'react-router'
|
||||
import _ from 'lodash'
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, number, shape, string} = PropTypes
|
||||
|
||||
const HostsTable = React.createClass({
|
||||
propTypes: {
|
||||
hosts: arrayOf(shape({
|
||||
name: string,
|
||||
cpu: number,
|
||||
load: number,
|
||||
apps: arrayOf(string.isRequired),
|
||||
})),
|
||||
hosts: arrayOf(
|
||||
shape({
|
||||
name: string,
|
||||
cpu: number,
|
||||
load: number,
|
||||
apps: arrayOf(string.isRequired),
|
||||
})
|
||||
),
|
||||
hostsLoading: bool,
|
||||
hostsError: string,
|
||||
source: shape({
|
||||
|
@ -36,7 +32,7 @@ const HostsTable = React.createClass({
|
|||
},
|
||||
|
||||
filter(allHosts, searchTerm) {
|
||||
return allHosts.filter((h) => {
|
||||
return allHosts.filter(h => {
|
||||
const apps = h.apps ? h.apps.join(', ') : ''
|
||||
// search each tag for the presence of the search term
|
||||
let tagResult = false
|
||||
|
@ -58,9 +54,9 @@ const HostsTable = React.createClass({
|
|||
sort(hosts, key, direction) {
|
||||
switch (direction) {
|
||||
case 'asc':
|
||||
return _.sortBy(hosts, (e) => e[key])
|
||||
return _.sortBy(hosts, e => e[key])
|
||||
case 'desc':
|
||||
return _.sortBy(hosts, (e) => e[key]).reverse()
|
||||
return _.sortBy(hosts, e => e[key]).reverse()
|
||||
default:
|
||||
return hosts
|
||||
}
|
||||
|
@ -73,7 +69,9 @@ const HostsTable = React.createClass({
|
|||
updateSort(key) {
|
||||
// if we're using the key, reverse order; otherwise, set it with ascending
|
||||
if (this.state.sortKey === key) {
|
||||
const reverseDirection = (this.state.sortDirection === 'asc' ? 'desc' : 'asc')
|
||||
const reverseDirection = this.state.sortDirection === 'asc'
|
||||
? 'desc'
|
||||
: 'asc'
|
||||
this.setState({sortDirection: reverseDirection})
|
||||
} else {
|
||||
this.setState({sortKey: key, sortDirection: 'asc'})
|
||||
|
@ -93,7 +91,11 @@ const HostsTable = React.createClass({
|
|||
render() {
|
||||
const {searchTerm, sortKey, sortDirection} = this.state
|
||||
const {hosts, hostsLoading, hostsError, source} = this.props
|
||||
const sortedHosts = this.sort(this.filter(hosts, searchTerm), sortKey, sortDirection)
|
||||
const sortedHosts = this.sort(
|
||||
this.filter(hosts, searchTerm),
|
||||
sortKey,
|
||||
sortDirection
|
||||
)
|
||||
const hostCount = sortedHosts.length
|
||||
|
||||
let hostsTitle
|
||||
|
@ -120,19 +122,40 @@ const HostsTable = React.createClass({
|
|||
<table className="table v-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th onClick={() => this.updateSort('name')} className={this.sortableClasses('name')}>Hostname</th>
|
||||
<th onClick={() => this.updateSort('deltaUptime')} className={this.sortableClasses('deltaUptime')} style={{width: '74px'}}>Status</th>
|
||||
<th onClick={() => this.updateSort('cpu')} className={this.sortableClasses('cpu')} style={{width: '70px'}}>CPU</th>
|
||||
<th onClick={() => this.updateSort('load')} className={this.sortableClasses('load')} style={{width: '68px'}}>Load</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('name')}
|
||||
className={this.sortableClasses('name')}
|
||||
>
|
||||
Hostname
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('deltaUptime')}
|
||||
className={this.sortableClasses('deltaUptime')}
|
||||
style={{width: '74px'}}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('cpu')}
|
||||
className={this.sortableClasses('cpu')}
|
||||
style={{width: '70px'}}
|
||||
>
|
||||
CPU
|
||||
</th>
|
||||
<th
|
||||
onClick={() => this.updateSort('load')}
|
||||
className={this.sortableClasses('load')}
|
||||
style={{width: '68px'}}
|
||||
>
|
||||
Load
|
||||
</th>
|
||||
<th>Apps</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
sortedHosts.map((h) => {
|
||||
return <HostRow key={h.name} host={h} source={source} />
|
||||
})
|
||||
}
|
||||
{sortedHosts.map(h => {
|
||||
return <HostRow key={h.name} host={h} source={source} />
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -173,17 +196,27 @@ const HostRow = React.createClass({
|
|||
|
||||
return (
|
||||
<tr>
|
||||
<td className="monotype"><Link to={`/sources/${source.id}/hosts/${name}`}>{name}</Link></td>
|
||||
<td style={{width: '74px'}}><div className={stateStr}></div></td>
|
||||
<td className="monotype" style={{width: '70px'}}>{isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}</td>
|
||||
<td className="monotype" style={{width: '68px'}}>{isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}</td>
|
||||
<td className="monotype">
|
||||
<Link to={`/sources/${source.id}/hosts/${name}`}>{name}</Link>
|
||||
</td>
|
||||
<td style={{width: '74px'}}><div className={stateStr} /></td>
|
||||
<td className="monotype" style={{width: '70px'}}>
|
||||
{isNaN(cpu) ? 'N/A' : `${cpu.toFixed(2)}%`}
|
||||
</td>
|
||||
<td className="monotype" style={{width: '68px'}}>
|
||||
{isNaN(load) ? 'N/A' : `${load.toFixed(2)}`}
|
||||
</td>
|
||||
<td className="monotype">
|
||||
{apps.map((app, index) => {
|
||||
return (
|
||||
<span key={app}>
|
||||
<Link
|
||||
style={{marginLeft: '2px'}}
|
||||
to={{pathname: `/sources/${source.id}/hosts/${name}`, query: {app}}}>
|
||||
to={{
|
||||
pathname: `/sources/${source.id}/hosts/${name}`,
|
||||
query: {app},
|
||||
}}
|
||||
>
|
||||
{app}
|
||||
</Link>
|
||||
{index === apps.length - 1 ? null : ', '}
|
||||
|
@ -231,7 +264,7 @@ const SearchBar = React.createClass({
|
|||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="input-group-addon">
|
||||
<span className="icon search" aria-hidden="true"></span>
|
||||
<span className="icon search" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -8,19 +8,18 @@ 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'
|
||||
import {getMappings, getAppsForHosts, getMeasurementsForHost, getAllHosts} from 'src/hosts/apis'
|
||||
import {
|
||||
getMappings,
|
||||
getAppsForHosts,
|
||||
getMeasurementsForHost,
|
||||
getAllHosts,
|
||||
} from 'src/hosts/apis'
|
||||
import {fetchLayouts} from 'shared/apis'
|
||||
|
||||
import {setAutoRefresh} from 'shared/actions/app'
|
||||
import {presentationButtonDispatcher} from 'shared/dispatchers'
|
||||
|
||||
const {
|
||||
shape,
|
||||
string,
|
||||
bool,
|
||||
func,
|
||||
number,
|
||||
} = PropTypes
|
||||
const {shape, string, bool, func, number} = PropTypes
|
||||
|
||||
export const HostPage = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -62,18 +61,27 @@ export const HostPage = React.createClass({
|
|||
const {data: {layouts}} = await fetchLayouts()
|
||||
const {data: {mappings}} = await getMappings()
|
||||
const hosts = await getAllHosts(source.links.proxy, source.telegraf)
|
||||
const newHosts = await getAppsForHosts(source.links.proxy, hosts, mappings, source.telegraf)
|
||||
const newHosts = await getAppsForHosts(
|
||||
source.links.proxy,
|
||||
hosts,
|
||||
mappings,
|
||||
source.telegraf
|
||||
)
|
||||
const measurements = await getMeasurementsForHost(source, params.hostID)
|
||||
|
||||
const host = newHosts[this.props.params.hostID]
|
||||
const focusedApp = location.query.app
|
||||
|
||||
const filteredLayouts = layouts.filter((layout) => {
|
||||
const filteredLayouts = layouts.filter(layout => {
|
||||
if (focusedApp) {
|
||||
return layout.app === focusedApp
|
||||
}
|
||||
|
||||
return host.apps && host.apps.includes(layout.app) && measurements.includes(layout.measurement)
|
||||
return (
|
||||
host.apps &&
|
||||
host.apps.includes(layout.app) &&
|
||||
measurements.includes(layout.measurement)
|
||||
)
|
||||
})
|
||||
|
||||
// only display hosts in the list if they match the current app
|
||||
|
@ -88,7 +96,7 @@ export const HostPage = React.createClass({
|
|||
},
|
||||
|
||||
handleChooseTimeRange({lower}) {
|
||||
const timeRange = timeRanges.find((range) => range.lower === lower)
|
||||
const timeRange = timeRanges.find(range => range.lower === lower)
|
||||
this.setState({timeRange})
|
||||
},
|
||||
|
||||
|
@ -96,7 +104,7 @@ export const HostPage = React.createClass({
|
|||
const {timeRange} = this.state
|
||||
const {source, autoRefresh} = this.props
|
||||
|
||||
const autoflowLayouts = layouts.filter((layout) => !!layout.autoflow)
|
||||
const autoflowLayouts = layouts.filter(layout => !!layout.autoflow)
|
||||
|
||||
const cellWidth = 4
|
||||
const cellHeight = 4
|
||||
|
@ -104,31 +112,33 @@ export const HostPage = React.createClass({
|
|||
|
||||
let cellCount = 0
|
||||
const autoflowCells = autoflowLayouts.reduce((allCells, layout) => {
|
||||
return allCells.concat(layout.cells.map((cell) => {
|
||||
const x = (cellCount * cellWidth % pageWidth)
|
||||
const y = Math.floor(cellCount * cellWidth / pageWidth) * cellHeight
|
||||
cellCount += 1
|
||||
return Object.assign(cell, {
|
||||
w: cellWidth,
|
||||
h: cellHeight,
|
||||
x,
|
||||
y,
|
||||
return allCells.concat(
|
||||
layout.cells.map(cell => {
|
||||
const x = cellCount * cellWidth % pageWidth
|
||||
const y = Math.floor(cellCount * cellWidth / pageWidth) * cellHeight
|
||||
cellCount += 1
|
||||
return Object.assign(cell, {
|
||||
w: cellWidth,
|
||||
h: cellHeight,
|
||||
x,
|
||||
y,
|
||||
})
|
||||
})
|
||||
}))
|
||||
)
|
||||
}, [])
|
||||
|
||||
const staticLayouts = layouts.filter((layout) => !layout.autoflow)
|
||||
const staticLayouts = layouts.filter(layout => !layout.autoflow)
|
||||
staticLayouts.unshift({cells: autoflowCells})
|
||||
|
||||
let translateY = 0
|
||||
const layoutCells = staticLayouts.reduce((allCells, layout) => {
|
||||
let maxY = 0
|
||||
layout.cells.forEach((cell) => {
|
||||
layout.cells.forEach(cell => {
|
||||
cell.y += translateY
|
||||
if (cell.y > translateY) {
|
||||
maxY = cell.y
|
||||
}
|
||||
cell.queries.forEach((q) => {
|
||||
cell.queries.forEach(q => {
|
||||
q.text = q.query
|
||||
q.database = source.telegraf
|
||||
})
|
||||
|
@ -179,19 +189,24 @@ export const HostPage = React.createClass({
|
|||
{Object.keys(hosts).map((host, i) => {
|
||||
return (
|
||||
<li key={i}>
|
||||
<Link to={`/sources/${id}/hosts/${host + appParam}`} className="role-option">
|
||||
<Link
|
||||
to={`/sources/${id}/hosts/${host + appParam}`}
|
||||
className="role-option"
|
||||
>
|
||||
{host}
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</DashboardHeader>
|
||||
<div className={classnames({
|
||||
'page-contents': true,
|
||||
'presentation-mode': inPresentationMode,
|
||||
})}>
|
||||
<div
|
||||
className={classnames({
|
||||
'page-contents': true,
|
||||
'presentation-mode': inPresentationMode,
|
||||
})}
|
||||
>
|
||||
<div className="container-fluid full-width dashboard">
|
||||
{ (layouts.length > 0) ? this.renderLayouts(layouts) : '' }
|
||||
{layouts.length > 0 ? this.renderLayouts(layouts) : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -199,12 +214,14 @@ export const HostPage = React.createClass({
|
|||
},
|
||||
})
|
||||
|
||||
const mapStateToProps = ({app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}}}) => ({
|
||||
const mapStateToProps = ({
|
||||
app: {ephemeral: {inPresentationMode}, persisted: {autoRefresh}},
|
||||
}) => ({
|
||||
inPresentationMode,
|
||||
autoRefresh,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleChooseAutoRefresh: bindActionCreators(setAutoRefresh, dispatch),
|
||||
handleClickPresentationButton: presentationButtonDispatcher(dispatch),
|
||||
})
|
||||
|
|
|
@ -10,7 +10,12 @@ import AlertsApp from 'src/alerts'
|
|||
import CheckSources from 'src/CheckSources'
|
||||
import {HostsPage, HostPage} from 'src/hosts'
|
||||
import {Login, UserIsAuthenticated, UserIsNotAuthenticated} from 'src/auth'
|
||||
import {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage} from 'src/kapacitor'
|
||||
import {
|
||||
KapacitorPage,
|
||||
KapacitorRulePage,
|
||||
KapacitorRulesPage,
|
||||
KapacitorTasksPage,
|
||||
} from 'src/kapacitor'
|
||||
import DataExplorer from 'src/data_explorer'
|
||||
import {DashboardsPage, DashboardPage} from 'src/dashboards'
|
||||
import {CreateSource, SourcePage, ManageSources} from 'src/sources'
|
||||
|
@ -22,7 +27,12 @@ import {loadLocalStorage} from './localStorage'
|
|||
import {getMe} from 'shared/apis'
|
||||
|
||||
import {disablePresentationMode} from 'shared/actions/app'
|
||||
import {authRequested, authReceived, meRequested, meReceived} from 'shared/actions/auth'
|
||||
import {
|
||||
authRequested,
|
||||
authReceived,
|
||||
meRequested,
|
||||
meReceived,
|
||||
} from 'shared/actions/auth'
|
||||
import {errorThrown} from 'shared/actions/errors'
|
||||
|
||||
import 'src/style/chronograf.scss'
|
||||
|
@ -51,7 +61,7 @@ browserHistory.listen(() => {
|
|||
dispatch(disablePresentationMode())
|
||||
})
|
||||
|
||||
window.addEventListener('keyup', (event) => {
|
||||
window.addEventListener('keyup', event => {
|
||||
if (event.key === 'Escape') {
|
||||
dispatch(disablePresentationMode())
|
||||
}
|
||||
|
@ -98,7 +108,10 @@ const Root = React.createClass({
|
|||
<Router history={history}>
|
||||
<Route path="/" component={UserIsAuthenticated(CheckSources)} />
|
||||
<Route path="login" component={UserIsNotAuthenticated(Login)} />
|
||||
<Route path="sources/new" component={UserIsAuthenticated(CreateSource)} />
|
||||
<Route
|
||||
path="sources/new"
|
||||
component={UserIsAuthenticated(CreateSource)}
|
||||
/>
|
||||
<Route path="sources/:sourceID" component={UserIsAuthenticated(App)}>
|
||||
<Route component={CheckSources}>
|
||||
<Route path="manage-sources" component={ManageSources} />
|
||||
|
|
|
@ -23,7 +23,7 @@ const exprStr = ({expr, val, type}) => {
|
|||
}
|
||||
}
|
||||
|
||||
const recurse = (root) => {
|
||||
const recurse = root => {
|
||||
const {expr} = root
|
||||
|
||||
if (expr === 'binary') {
|
||||
|
@ -40,15 +40,8 @@ const recurse = (root) => {
|
|||
return exprStr(root)
|
||||
}
|
||||
|
||||
export const toString = (ast) => {
|
||||
const {
|
||||
fields,
|
||||
sources,
|
||||
condition,
|
||||
groupBy,
|
||||
orderbys,
|
||||
limits,
|
||||
} = ast
|
||||
export const toString = ast => {
|
||||
const {fields, sources, condition, groupBy, orderbys, limits} = ast
|
||||
|
||||
const strs = ['SELECT']
|
||||
|
||||
|
@ -108,9 +101,13 @@ export const toString = (ast) => {
|
|||
// ORDER BY
|
||||
if (orderbys && orderbys.length) {
|
||||
strs.push('ORDER BY')
|
||||
strs.push(orderbys.map(({name, order}) => {
|
||||
return `${name} ${order === 'descending' ? 'DESC' : 'ASC'}`
|
||||
}).join(','))
|
||||
strs.push(
|
||||
orderbys
|
||||
.map(({name, order}) => {
|
||||
return `${name} ${order === 'descending' ? 'DESC' : 'ASC'}`
|
||||
})
|
||||
.join(',')
|
||||
)
|
||||
}
|
||||
|
||||
// LIMIT
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {toString} from './ast'
|
||||
|
||||
const InfluxQL = (ast) => {
|
||||
const InfluxQL = ast => {
|
||||
return {
|
||||
// select: () =>
|
||||
toString: () => toString(ast),
|
||||
|
|
|
@ -9,8 +9,8 @@ import {
|
|||
} from 'src/kapacitor/apis'
|
||||
|
||||
export function fetchRule(source, ruleID) {
|
||||
return (dispatch) => {
|
||||
getActiveKapacitor(source).then((kapacitor) => {
|
||||
return dispatch => {
|
||||
getActiveKapacitor(source).then(kapacitor => {
|
||||
getRule(kapacitor, ruleID).then(({data: rule}) => {
|
||||
dispatch({
|
||||
type: 'LOAD_RULE',
|
||||
|
@ -31,7 +31,7 @@ export function fetchRule(source, ruleID) {
|
|||
}
|
||||
|
||||
export function loadDefaultRule() {
|
||||
return (dispatch) => {
|
||||
return dispatch => {
|
||||
const queryID = uuid.v4()
|
||||
dispatch({
|
||||
type: 'LOAD_DEFAULT_RULE',
|
||||
|
@ -49,7 +49,7 @@ export function loadDefaultRule() {
|
|||
}
|
||||
|
||||
export function fetchRules(kapacitor) {
|
||||
return (dispatch) => {
|
||||
return dispatch => {
|
||||
getRules(kapacitor).then(({data: {rules}}) => {
|
||||
dispatch({
|
||||
type: 'LOAD_RULES',
|
||||
|
@ -153,22 +153,34 @@ export function updateRuleStatusSuccess(ruleID, status) {
|
|||
}
|
||||
|
||||
export function deleteRule(rule) {
|
||||
return (dispatch) => {
|
||||
deleteRuleAPI(rule).then(() => {
|
||||
dispatch(deleteRuleSuccess(rule.id))
|
||||
dispatch(publishNotification('success', `${rule.name} deleted successfully`))
|
||||
}).catch(() => {
|
||||
dispatch(publishNotification('error', `${rule.name} could not be deleted`))
|
||||
})
|
||||
return dispatch => {
|
||||
deleteRuleAPI(rule)
|
||||
.then(() => {
|
||||
dispatch(deleteRuleSuccess(rule.id))
|
||||
dispatch(
|
||||
publishNotification('success', `${rule.name} deleted successfully`)
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(
|
||||
publishNotification('error', `${rule.name} could not be deleted`)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function updateRuleStatus(rule, status) {
|
||||
return (dispatch) => {
|
||||
updateRuleStatusAPI(rule, status).then(() => {
|
||||
dispatch(publishNotification('success', `${rule.name} ${status} successfully`))
|
||||
}).catch(() => {
|
||||
dispatch(publishNotification('error', `${rule.name} could not be ${status}`))
|
||||
})
|
||||
return dispatch => {
|
||||
updateRuleStatusAPI(rule, status)
|
||||
.then(() => {
|
||||
dispatch(
|
||||
publishNotification('success', `${rule.name} ${status} successfully`)
|
||||
)
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(
|
||||
publishNotification('error', `${rule.name} could not be ${status}`)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,7 +77,10 @@ export const DataSection = React.createClass({
|
|||
|
||||
render() {
|
||||
const {query, timeRange: {lower}} = this.props
|
||||
const statement = query.rawText || buildInfluxQLQuery({lower}, query) || 'Build a query below'
|
||||
const statement =
|
||||
query.rawText ||
|
||||
buildInfluxQLQuery({lower}, query) ||
|
||||
'Build a query below'
|
||||
|
||||
return (
|
||||
<div className="kapacitor-rule-section kapacitor-metric-selector">
|
||||
|
|
|
@ -37,8 +37,8 @@ class KapacitorForm extends Component {
|
|||
placeholder={url}
|
||||
value={url}
|
||||
onChange={onInputChange}
|
||||
spellCheck="false">
|
||||
</input>
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="name">Name</label>
|
||||
|
@ -49,8 +49,8 @@ class KapacitorForm extends Component {
|
|||
placeholder={name}
|
||||
value={name}
|
||||
onChange={onInputChange}
|
||||
spellCheck="false">
|
||||
</input>
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="username">Username</label>
|
||||
|
@ -61,8 +61,8 @@ class KapacitorForm extends Component {
|
|||
placeholder="username"
|
||||
value={username}
|
||||
onChange={onInputChange}
|
||||
spellCheck="false">
|
||||
</input>
|
||||
spellCheck="false"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label htmlFor="password">Password</label>
|
||||
|
@ -80,8 +80,16 @@ class KapacitorForm extends Component {
|
|||
</div>
|
||||
|
||||
<div className="form-group form-group-submit col-xs-12 text-center">
|
||||
<button className="btn btn-info" type="button" onClick={onReset}>Reset</button>
|
||||
<button className="btn btn-success" type="submit">Connect</button>
|
||||
<button
|
||||
className="btn btn-info"
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
>
|
||||
Reset
|
||||
</button>
|
||||
<button className="btn btn-success" type="submit">
|
||||
Connect
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -102,7 +110,13 @@ class KapacitorForm extends Component {
|
|||
const {exists, kapacitor, addFlashMessage, source} = this.props
|
||||
|
||||
if (exists) {
|
||||
return <AlertTabs source={source} kapacitor={kapacitor} addFlashMessage={addFlashMessage} />
|
||||
return (
|
||||
<AlertTabs
|
||||
source={source}
|
||||
kapacitor={kapacitor}
|
||||
addFlashMessage={addFlashMessage}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -111,20 +125,17 @@ class KapacitorForm extends Component {
|
|||
<h2 className="panel-title">Configure Alert Endpoints</h2>
|
||||
</div>
|
||||
<div className="panel-body">
|
||||
<br/>
|
||||
<p className="text-center">Set your Kapacitor connection info to configure alerting endpoints.</p>
|
||||
<br />
|
||||
<p className="text-center">
|
||||
Set your Kapacitor connection info to configure alerting endpoints.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
bool,
|
||||
} = PropTypes
|
||||
const {func, shape, string, bool} = PropTypes
|
||||
|
||||
KapacitorForm.propTypes = {
|
||||
onSubmit: func.isRequired,
|
||||
|
|
|
@ -33,8 +33,16 @@ export const KapacitorRule = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
const {queryActions, source, enabledAlerts, queryConfigs, query,
|
||||
rule, kapacitorActions, isEditing} = this.props
|
||||
const {
|
||||
queryActions,
|
||||
source,
|
||||
enabledAlerts,
|
||||
queryConfigs,
|
||||
query,
|
||||
rule,
|
||||
kapacitorActions,
|
||||
isEditing,
|
||||
} = this.props
|
||||
const {chooseTrigger, updateRuleValues} = kapacitorActions
|
||||
const {timeRange} = this.state
|
||||
|
||||
|
@ -54,15 +62,29 @@ export const KapacitorRule = React.createClass({
|
|||
<div className="row">
|
||||
<div className="col-xs-12">
|
||||
<div className="rule-builder">
|
||||
<DataSection timeRange={timeRange} source={source} query={query} actions={queryActions} />
|
||||
<DataSection
|
||||
timeRange={timeRange}
|
||||
source={source}
|
||||
query={query}
|
||||
actions={queryActions}
|
||||
/>
|
||||
<ValuesSection
|
||||
rule={rule}
|
||||
query={queryConfigs[rule.queryID]}
|
||||
onChooseTrigger={chooseTrigger}
|
||||
onUpdateValues={updateRuleValues}
|
||||
/>
|
||||
<RuleGraph timeRange={timeRange} source={source} query={query} rule={rule} />
|
||||
<RuleMessage rule={rule} actions={kapacitorActions} enabledAlerts={enabledAlerts} />
|
||||
<RuleGraph
|
||||
timeRange={timeRange}
|
||||
source={source}
|
||||
query={query}
|
||||
rule={rule}
|
||||
/>
|
||||
<RuleMessage
|
||||
rule={rule}
|
||||
actions={kapacitorActions}
|
||||
enabledAlerts={enabledAlerts}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -73,24 +95,36 @@ export const KapacitorRule = React.createClass({
|
|||
},
|
||||
|
||||
handleChooseTimeRange({lower}) {
|
||||
const timeRange = timeRanges.find((range) => range.lower === lower)
|
||||
const timeRange = timeRanges.find(range => range.lower === lower)
|
||||
this.setState({timeRange})
|
||||
},
|
||||
|
||||
handleCreate() {
|
||||
const {addFlashMessage, queryConfigs, rule, source, router, kapacitor} = this.props
|
||||
const {
|
||||
addFlashMessage,
|
||||
queryConfigs,
|
||||
rule,
|
||||
source,
|
||||
router,
|
||||
kapacitor,
|
||||
} = this.props
|
||||
|
||||
const newRule = Object.assign({}, rule, {
|
||||
query: queryConfigs[rule.queryID],
|
||||
})
|
||||
delete newRule.queryID
|
||||
|
||||
createRule(kapacitor, newRule).then(() => {
|
||||
router.push(`/sources/${source.id}/alert-rules`)
|
||||
addFlashMessage({type: 'success', text: 'Rule successfully created'})
|
||||
}).catch(() => {
|
||||
addFlashMessage({type: 'error', text: 'There was a problem creating the rule'})
|
||||
})
|
||||
createRule(kapacitor, newRule)
|
||||
.then(() => {
|
||||
router.push(`/sources/${source.id}/alert-rules`)
|
||||
addFlashMessage({type: 'success', text: 'Rule successfully created'})
|
||||
})
|
||||
.catch(() => {
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was a problem creating the rule',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
handleEdit() {
|
||||
|
@ -100,11 +134,16 @@ export const KapacitorRule = React.createClass({
|
|||
query: queryConfigs[rule.queryID],
|
||||
})
|
||||
|
||||
editRule(updatedRule).then(() => {
|
||||
addFlashMessage({type: 'success', text: 'Rule successfully updated!'})
|
||||
}).catch(() => {
|
||||
addFlashMessage({type: 'error', text: 'There was a problem updating the rule'})
|
||||
})
|
||||
editRule(updatedRule)
|
||||
.then(() => {
|
||||
addFlashMessage({type: 'success', text: 'Rule successfully updated!'})
|
||||
})
|
||||
.catch(() => {
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was a problem updating the rule',
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
validationError() {
|
||||
|
|
|
@ -30,10 +30,15 @@ const KapacitorRules = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<PageContents source={source} >
|
||||
<PageContents source={source}>
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<h2 className="panel-title">Alert Rules</h2>
|
||||
<Link to={`/sources/${source.id}/alert-rules/new`} className="btn btn-sm btn-primary">Create Rule</Link>
|
||||
<Link
|
||||
to={`/sources/${source.id}/alert-rules/new`}
|
||||
className="btn btn-sm btn-primary"
|
||||
>
|
||||
Create Rule
|
||||
</Link>
|
||||
</div>
|
||||
<KapacitorRulesTable
|
||||
source={source}
|
||||
|
@ -71,13 +76,7 @@ const PageContents = ({children, source}) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
node,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, shape, node} = PropTypes
|
||||
|
||||
KapacitorRules.propTypes = {
|
||||
source: shape(),
|
||||
|
|
|
@ -12,15 +12,21 @@ const KapacitorRulesTable = ({source, rules, onDelete, onChangeRuleStatus}) => {
|
|||
<th>Message</th>
|
||||
<th>Alerts</th>
|
||||
<th className="text-center">Enabled</th>
|
||||
<th></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
rules.map((rule) => {
|
||||
return <RuleRow key={rule.id} rule={rule} source={source} onDelete={onDelete} onChangeRuleStatus={onChangeRuleStatus} />
|
||||
})
|
||||
}
|
||||
{rules.map(rule => {
|
||||
return (
|
||||
<RuleRow
|
||||
key={rule.id}
|
||||
rule={rule}
|
||||
source={source}
|
||||
onDelete={onDelete}
|
||||
onChangeRuleStatus={onChangeRuleStatus}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -30,7 +36,11 @@ const KapacitorRulesTable = ({source, rules, onDelete, onChangeRuleStatus}) => {
|
|||
const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) => {
|
||||
return (
|
||||
<tr key={rule.id}>
|
||||
<td className="monotype"><Link to={`/sources/${source.id}/alert-rules/${rule.id}`}>{rule.name}</Link></td>
|
||||
<td className="monotype">
|
||||
<Link to={`/sources/${source.id}/alert-rules/${rule.id}`}>
|
||||
{rule.name}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="monotype">{rule.trigger}</td>
|
||||
<td className="monotype">{rule.message}</td>
|
||||
<td className="monotype">{rule.alerts.join(', ')}</td>
|
||||
|
@ -43,19 +53,22 @@ const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) => {
|
|||
defaultChecked={rule.status === 'enabled'}
|
||||
onClick={() => onChangeRuleStatus(rule)}
|
||||
/>
|
||||
<label htmlFor={`kapacitor-enabled ${rule.id}`}></label>
|
||||
<label htmlFor={`kapacitor-enabled ${rule.id}`} />
|
||||
</div>
|
||||
</td>
|
||||
<td className="text-right"><button className="btn btn-danger btn-xs" onClick={() => onDelete(rule)}>Delete</button></td>
|
||||
<td className="text-right">
|
||||
<button
|
||||
className="btn btn-danger btn-xs"
|
||||
onClick={() => onDelete(rule)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, shape} = PropTypes
|
||||
|
||||
KapacitorRulesTable.propTypes = {
|
||||
source: shape(),
|
||||
|
|
|
@ -79,7 +79,8 @@ export const RuleGraph = React.createClass({
|
|||
|
||||
case 'not equal to':
|
||||
case 'equal to': {
|
||||
const width = (theOnePercent) * (dygraph.yAxisRange()[1] - dygraph.yAxisRange()[0])
|
||||
const width =
|
||||
theOnePercent * (dygraph.yAxisRange()[1] - dygraph.yAxisRange()[0])
|
||||
highlightStart = +rule.values.value - width
|
||||
highlightEnd = +rule.values.value + width
|
||||
break
|
||||
|
@ -105,7 +106,9 @@ export const RuleGraph = React.createClass({
|
|||
const bottom = dygraph.toDomYCoord(highlightStart)
|
||||
const top = dygraph.toDomYCoord(highlightEnd)
|
||||
|
||||
canvas.fillStyle = rule.values.operator === 'outside range' ? 'rgba(41, 41, 51, 1)' : 'rgba(78, 216, 160, 0.3)'
|
||||
canvas.fillStyle = rule.values.operator === 'outside range'
|
||||
? 'rgba(41, 41, 51, 1)'
|
||||
: 'rgba(78, 216, 160, 0.3)'
|
||||
canvas.fillRect(area.x, top, area.w, bottom - top)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -58,19 +58,41 @@ export const RuleHeader = React.createClass({
|
|||
},
|
||||
|
||||
renderSave() {
|
||||
const {validationError, onSave, timeRange, onChooseTimeRange, source} = this.props
|
||||
const saveButton = validationError ?
|
||||
(<button className="btn btn-success btn-sm disabled" data-for="save-kapacitor-tooltip" data-tip={validationError}>
|
||||
Save Rule
|
||||
</button>) :
|
||||
<button className="btn btn-success btn-sm" onClick={onSave}>Save Rule</button>
|
||||
const {
|
||||
validationError,
|
||||
onSave,
|
||||
timeRange,
|
||||
onChooseTimeRange,
|
||||
source,
|
||||
} = this.props
|
||||
const saveButton = validationError
|
||||
? <button
|
||||
className="btn btn-success btn-sm disabled"
|
||||
data-for="save-kapacitor-tooltip"
|
||||
data-tip={validationError}
|
||||
>
|
||||
Save Rule
|
||||
</button>
|
||||
: <button className="btn btn-success btn-sm" onClick={onSave}>
|
||||
Save Rule
|
||||
</button>
|
||||
|
||||
return (
|
||||
<div className="page-header__right">
|
||||
<SourceIndicator sourceName={source.name} />
|
||||
<TimeRangeDropdown onChooseTimeRange={onChooseTimeRange} selected={timeRange} />
|
||||
<TimeRangeDropdown
|
||||
onChooseTimeRange={onChooseTimeRange}
|
||||
selected={timeRange}
|
||||
/>
|
||||
{saveButton}
|
||||
<ReactTooltip id="save-kapacitor-tooltip" effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip kapacitor-tooltip place-bottom" />
|
||||
<ReactTooltip
|
||||
id="save-kapacitor-tooltip"
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: 2}}
|
||||
place="bottom"
|
||||
class="influx-tooltip kapacitor-tooltip place-bottom"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
@ -79,21 +101,34 @@ export const RuleHeader = React.createClass({
|
|||
const {rule} = this.props
|
||||
const {isEditingName} = this.state
|
||||
|
||||
const name = isEditingName ?
|
||||
(<input
|
||||
className="page-header--editing kapacitor-theme"
|
||||
autoFocus={true}
|
||||
defaultValue={rule.name}
|
||||
ref={r => this.ruleName = r}
|
||||
onKeyDown={(e) => this.handleEditName(e, rule)}
|
||||
onBlur={() => this.handleEditNameBlur(rule)}
|
||||
placeholder="Name your rule"
|
||||
/>) :
|
||||
(<h1 className="page-header--editable kapacitor-theme" onClick={this.toggleEditName} data-for="rename-kapacitor-tooltip" data-tip="Click to Rename">
|
||||
{rule.name}
|
||||
<span className="icon pencil"></span>
|
||||
<ReactTooltip id="rename-kapacitor-tooltip" delayShow={200} effect="solid" html={true} offset={{top: 2}} place="bottom" class="influx-tooltip kapacitor-tooltip place-bottom" />
|
||||
</h1>)
|
||||
const name = isEditingName
|
||||
? <input
|
||||
className="page-header--editing kapacitor-theme"
|
||||
autoFocus={true}
|
||||
defaultValue={rule.name}
|
||||
ref={r => (this.ruleName = r)}
|
||||
onKeyDown={e => this.handleEditName(e, rule)}
|
||||
onBlur={() => this.handleEditNameBlur(rule)}
|
||||
placeholder="Name your rule"
|
||||
/>
|
||||
: <h1
|
||||
className="page-header--editable kapacitor-theme"
|
||||
onClick={this.toggleEditName}
|
||||
data-for="rename-kapacitor-tooltip"
|
||||
data-tip="Click to Rename"
|
||||
>
|
||||
{rule.name}
|
||||
<span className="icon pencil" />
|
||||
<ReactTooltip
|
||||
id="rename-kapacitor-tooltip"
|
||||
delayShow={200}
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: 2}}
|
||||
place="bottom"
|
||||
class="influx-tooltip kapacitor-tooltip place-bottom"
|
||||
/>
|
||||
</h1>
|
||||
|
||||
return (
|
||||
<div className="page-header__left">
|
||||
|
@ -101,7 +136,6 @@ export const RuleHeader = React.createClass({
|
|||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
export default RuleHeader
|
||||
|
|
|
@ -2,19 +2,12 @@ import React, {PropTypes} from 'react'
|
|||
import classnames from 'classnames'
|
||||
import ReactTooltip from 'react-tooltip'
|
||||
|
||||
import RuleMessageAlertConfig from 'src/kapacitor/components/RuleMessageAlertConfig'
|
||||
import RuleMessageAlertConfig
|
||||
from 'src/kapacitor/components/RuleMessageAlertConfig'
|
||||
|
||||
import {
|
||||
RULE_MESSAGE_TEMPLATES as templates,
|
||||
DEFAULT_ALERTS,
|
||||
} from '../constants'
|
||||
import {RULE_MESSAGE_TEMPLATES as templates, DEFAULT_ALERTS} from '../constants'
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
|
||||
export const RuleMessage = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -47,13 +40,15 @@ export const RuleMessage = React.createClass({
|
|||
|
||||
render() {
|
||||
const {rule, actions, enabledAlerts} = this.props
|
||||
const defaultAlertEndpoints = DEFAULT_ALERTS.map((text) => {
|
||||
const defaultAlertEndpoints = DEFAULT_ALERTS.map(text => {
|
||||
return {text, ruleID: rule.id}
|
||||
})
|
||||
|
||||
const alerts = enabledAlerts.map((text) => {
|
||||
return {text, ruleID: rule.id}
|
||||
}).concat(defaultAlertEndpoints)
|
||||
const alerts = enabledAlerts
|
||||
.map(text => {
|
||||
return {text, ruleID: rule.id}
|
||||
})
|
||||
.concat(defaultAlertEndpoints)
|
||||
|
||||
const selectedAlert = rule.alerts[0] || alerts[0].text
|
||||
|
||||
|
@ -64,15 +59,17 @@ export const RuleMessage = React.createClass({
|
|||
<div className="kapacitor-values-tabs">
|
||||
<p>Send this Alert to:</p>
|
||||
<ul className="btn-group btn-group-lg tab-group">
|
||||
{alerts.map(alert =>
|
||||
{alerts.map(alert => (
|
||||
<li
|
||||
key={alert.text}
|
||||
className={classnames('btn tab', {active: alert.text === selectedAlert})}
|
||||
className={classnames('btn tab', {
|
||||
active: alert.text === selectedAlert,
|
||||
})}
|
||||
onClick={() => this.handleChooseAlert(alert)}
|
||||
>
|
||||
{alert.text}
|
||||
</li>
|
||||
)}
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<RuleMessageAlertConfig
|
||||
|
@ -80,39 +77,46 @@ export const RuleMessage = React.createClass({
|
|||
alert={selectedAlert}
|
||||
rule={rule}
|
||||
/>
|
||||
{
|
||||
selectedAlert === 'smtp' ?
|
||||
<div className="alert-message--email-body">
|
||||
<textarea
|
||||
className="alert-text details"
|
||||
placeholder="Email body text goes here"
|
||||
ref={(r) => this.details = r}
|
||||
onChange={() => actions.updateDetails(rule.id, this.details.value)}
|
||||
value={rule.details}
|
||||
/>
|
||||
</div> : null
|
||||
}
|
||||
{selectedAlert === 'smtp'
|
||||
? <div className="alert-message--email-body">
|
||||
<textarea
|
||||
className="alert-text details"
|
||||
placeholder="Email body text goes here"
|
||||
ref={r => (this.details = r)}
|
||||
onChange={() =>
|
||||
actions.updateDetails(rule.id, this.details.value)}
|
||||
value={rule.details}
|
||||
/>
|
||||
</div>
|
||||
: null}
|
||||
<textarea
|
||||
className="alert-text message"
|
||||
ref={(r) => this.message = r}
|
||||
ref={r => (this.message = r)}
|
||||
onChange={() => actions.updateMessage(rule.id, this.message.value)}
|
||||
placeholder='Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields "value" }}'
|
||||
placeholder="Example: {{ .ID }} is {{ .Level }} value: {{ index .Fields "value" }}"
|
||||
value={rule.message}
|
||||
/>
|
||||
<div className="alert-message--formatting">
|
||||
<p>Templates:</p>
|
||||
{
|
||||
Object.keys(templates).map(t => {
|
||||
return (
|
||||
{Object.keys(templates).map(t => {
|
||||
return (
|
||||
<CodeData
|
||||
key={t}
|
||||
template={templates[t]}
|
||||
onClickTemplate={() => actions.updateMessage(rule.id, `${this.message.value} ${templates[t].label}`)}
|
||||
onClickTemplate={() =>
|
||||
actions.updateMessage(
|
||||
rule.id,
|
||||
`${this.message.value} ${templates[t].label}`
|
||||
)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
<ReactTooltip effect="solid" html={true} offset={{top: -4}} class="influx-tooltip kapacitor-tooltip" />
|
||||
)
|
||||
})}
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
html={true}
|
||||
offset={{top: -4}}
|
||||
class="influx-tooltip kapacitor-tooltip"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -133,9 +137,7 @@ const CodeData = React.createClass({
|
|||
const {onClickTemplate, template} = this.props
|
||||
const {label, text} = template
|
||||
|
||||
return (
|
||||
<code data-tip={text} onClick={onClickTemplate}>{label}</code>
|
||||
)
|
||||
return <code data-tip={text} onClick={onClickTemplate}>{label}</code>
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
@ -6,15 +6,11 @@ import {
|
|||
ALERT_NODES_ACCESSORS,
|
||||
} from '../constants'
|
||||
|
||||
const RuleMessageAlertConfig = ({
|
||||
updateAlertNodes,
|
||||
alert,
|
||||
rule,
|
||||
}) => {
|
||||
if (!Object.keys(DEFAULT_ALERT_PLACEHOLDERS).find((a) => a === alert)) {
|
||||
const RuleMessageAlertConfig = ({updateAlertNodes, alert, rule}) => {
|
||||
if (!Object.keys(DEFAULT_ALERT_PLACEHOLDERS).find(a => a === alert)) {
|
||||
return null
|
||||
}
|
||||
if (!Object.keys(DEFAULT_ALERT_LABELS).find((a) => a === alert)) {
|
||||
if (!Object.keys(DEFAULT_ALERT_LABELS).find(a => a === alert)) {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
|
@ -25,7 +21,7 @@ const RuleMessageAlertConfig = ({
|
|||
className="form-control size-486 form-control--green input-sm"
|
||||
type="text"
|
||||
placeholder={DEFAULT_ALERT_PLACEHOLDERS[alert]}
|
||||
onChange={(e) => updateAlertNodes(rule.id, alert, e.target.value)}
|
||||
onChange={e => updateAlertNodes(rule.id, alert, e.target.value)}
|
||||
value={ALERT_NODES_ACCESSORS[alert](rule)}
|
||||
autoComplete="off"
|
||||
spellCheck="false"
|
||||
|
@ -34,11 +30,7 @@ const RuleMessageAlertConfig = ({
|
|||
)
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
RuleMessageAlertConfig.propTypes = {
|
||||
updateAlertNodes: func.isRequired,
|
||||
|
|
|
@ -5,7 +5,7 @@ import {OPERATORS, PERIODS, CHANGES, SHIFTS} from 'src/kapacitor/constants'
|
|||
import _ from 'lodash'
|
||||
|
||||
const TABS = ['Threshold', 'Relative', 'Deadman']
|
||||
const mapToItems = (arr, type) => arr.map((text) => ({text, type}))
|
||||
const mapToItems = (arr, type) => arr.map(text => ({text, type}))
|
||||
|
||||
export const ValuesSection = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -32,7 +32,11 @@ export const ValuesSection = React.createClass({
|
|||
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<Threshold rule={rule} query={query} onChange={this.handleValuesChange} />
|
||||
<Threshold
|
||||
rule={rule}
|
||||
query={query}
|
||||
onChange={this.handleValuesChange}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<Relative rule={rule} onChange={this.handleValuesChange} />
|
||||
|
@ -76,7 +80,6 @@ const Threshold = React.createClass({
|
|||
query: PropTypes.shape({}).isRequired,
|
||||
},
|
||||
|
||||
|
||||
handleDropdownChange(item) {
|
||||
this.props.onChange({...this.props.rule.values, [item.type]: item.text})
|
||||
},
|
||||
|
@ -98,13 +101,33 @@ const Threshold = React.createClass({
|
|||
return (
|
||||
<div className="value-selector">
|
||||
<p>Send Alert where</p>
|
||||
<span>{query.fields.length ? query.fields[0].field : 'Select a Time-Series'}</span>
|
||||
<span>
|
||||
{query.fields.length ? query.fields[0].field : 'Select a Time-Series'}
|
||||
</span>
|
||||
<p>is</p>
|
||||
<Dropdown className="size-176 dropdown-kapacitor" items={operators} selected={operator} onChoose={this.handleDropdownChange} />
|
||||
<input className="form-control input-sm size-166 form-control--green" type="text" spellCheck="false" ref={(r) => this.valueInput = r} defaultValue={value} onKeyUp={this.handleInputChange} />
|
||||
{ (operator === 'inside range' || operator === 'outside range') &&
|
||||
<input className="form-control input-sm size-166 form-control--green" type="text" spellCheck="false" ref={(r) => this.valueRangeInput = r} defaultValue={rangeValue} onKeyUp={this.handleInputChange} />
|
||||
}
|
||||
<Dropdown
|
||||
className="size-176 dropdown-kapacitor"
|
||||
items={operators}
|
||||
selected={operator}
|
||||
onChoose={this.handleDropdownChange}
|
||||
/>
|
||||
<input
|
||||
className="form-control input-sm size-166 form-control--green"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
ref={r => (this.valueInput = r)}
|
||||
defaultValue={value}
|
||||
onKeyUp={this.handleInputChange}
|
||||
/>
|
||||
{(operator === 'inside range' || operator === 'outside range') &&
|
||||
<input
|
||||
className="form-control input-sm size-166 form-control--green"
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
ref={r => (this.valueRangeInput = r)}
|
||||
defaultValue={rangeValue}
|
||||
onKeyUp={this.handleInputChange}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
@ -141,21 +164,36 @@ const Relative = React.createClass({
|
|||
return (
|
||||
<div className="value-selector">
|
||||
<p>Send Alert when</p>
|
||||
<Dropdown className="size-106 dropdown-kapacitor"items={changes} selected={change} onChoose={this.handleDropdownChange} />
|
||||
<Dropdown
|
||||
className="size-106 dropdown-kapacitor"
|
||||
items={changes}
|
||||
selected={change}
|
||||
onChoose={this.handleDropdownChange}
|
||||
/>
|
||||
<p>compared to previous</p>
|
||||
<Dropdown className="size-66 dropdown-kapacitor" items={shifts} selected={shift} onChoose={this.handleDropdownChange} />
|
||||
<Dropdown
|
||||
className="size-66 dropdown-kapacitor"
|
||||
items={shifts}
|
||||
selected={shift}
|
||||
onChoose={this.handleDropdownChange}
|
||||
/>
|
||||
<p>is</p>
|
||||
<Dropdown className="size-176 dropdown-kapacitor" items={operators} selected={operator} onChoose={this.handleDropdownChange} />
|
||||
<Dropdown
|
||||
className="size-176 dropdown-kapacitor"
|
||||
items={operators}
|
||||
selected={operator}
|
||||
onChoose={this.handleDropdownChange}
|
||||
/>
|
||||
<input
|
||||
className="form-control input-sm size-166 form-control--green"
|
||||
ref={(r) => this.input = r}
|
||||
ref={r => (this.input = r)}
|
||||
defaultValue={value}
|
||||
onKeyUp={this.handleInputChange}
|
||||
required={true}
|
||||
type="text"
|
||||
spellCheck="false"
|
||||
/>
|
||||
<p>{ change === CHANGES[1] ? '%' : '' }</p>
|
||||
<p>{change === CHANGES[1] ? '%' : ''}</p>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
@ -176,14 +214,19 @@ const Deadman = React.createClass({
|
|||
},
|
||||
|
||||
render() {
|
||||
const periods = PERIODS.map((text) => {
|
||||
const periods = PERIODS.map(text => {
|
||||
return {text}
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="value-selector">
|
||||
<p>Send Alert if Data is missing for</p>
|
||||
<Dropdown className="size-66 dropdown-kapacitor" items={periods} selected={this.props.rule.values.period} onChoose={this.handleChange} />
|
||||
<Dropdown
|
||||
className="size-66 dropdown-kapacitor"
|
||||
items={periods}
|
||||
selected={this.props.rule.values.period}
|
||||
onChoose={this.handleChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -41,7 +41,7 @@ const AlertaConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="environment"
|
||||
type="text"
|
||||
ref={r => this.environment = r}
|
||||
ref={r => (this.environment = r)}
|
||||
defaultValue={environment || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -52,7 +52,7 @@ const AlertaConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="origin"
|
||||
type="text"
|
||||
ref={r => this.origin = r}
|
||||
ref={r => (this.origin = r)}
|
||||
defaultValue={origin || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -62,7 +62,7 @@ const AlertaConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => this.token = r}
|
||||
refFunc={r => (this.token = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -72,7 +72,7 @@ const AlertaConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="url"
|
||||
type="text"
|
||||
ref={r => this.url = r}
|
||||
ref={r => (this.url = r)}
|
||||
defaultValue={url || ''}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -47,7 +47,7 @@ const HipchatConfig = React.createClass({
|
|||
id="url"
|
||||
type="text"
|
||||
placeholder="your-subdomain"
|
||||
ref={r => this.url = r}
|
||||
ref={r => (this.url = r)}
|
||||
defaultValue={subdomain && subdomain.length ? subdomain : ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -59,7 +59,7 @@ const HipchatConfig = React.createClass({
|
|||
id="room"
|
||||
type="text"
|
||||
placeholder="your-hipchat-room"
|
||||
ref={r => this.room = r}
|
||||
ref={r => (this.room = r)}
|
||||
defaultValue={room || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -72,7 +72,7 @@ const HipchatConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => this.token = r}
|
||||
refFunc={r => (this.token = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ const OpsGenieConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={apiKey}
|
||||
id="api-key"
|
||||
refFunc={r => this.apiKey = r}
|
||||
refFunc={r => (this.apiKey = r)}
|
||||
/>
|
||||
<label className="form-helper">
|
||||
Note: a value of
|
||||
|
@ -145,7 +145,7 @@ const TagInput = React.createClass({
|
|||
className="form-control"
|
||||
id={title}
|
||||
type="text"
|
||||
ref={r => this.input = r}
|
||||
ref={r => (this.input = r)}
|
||||
onKeyDown={this.handleAddTag}
|
||||
/>
|
||||
<Tags tags={tags} onDeleteTag={onDeleteTag} />
|
||||
|
|
|
@ -35,7 +35,7 @@ const PagerDutyConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="service-key"
|
||||
type="text"
|
||||
ref={r => this.serviceKey = r}
|
||||
ref={r => (this.serviceKey = r)}
|
||||
defaultValue={serviceKey || ''}
|
||||
/>
|
||||
<label className="form-helper">
|
||||
|
@ -53,7 +53,7 @@ const PagerDutyConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="url"
|
||||
type="text"
|
||||
ref={r => this.url = r}
|
||||
ref={r => (this.url = r)}
|
||||
defaultValue={url || ''}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -39,7 +39,7 @@ const SMTPConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="smtp-host"
|
||||
type="text"
|
||||
ref={r => this.host = r}
|
||||
ref={r => (this.host = r)}
|
||||
defaultValue={host || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@ const SMTPConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="smtp-port"
|
||||
type="text"
|
||||
ref={r => this.port = r}
|
||||
ref={r => (this.port = r)}
|
||||
defaultValue={port || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -62,7 +62,7 @@ const SMTPConfig = React.createClass({
|
|||
id="smtp-from"
|
||||
placeholder="email@domain.com"
|
||||
type="text"
|
||||
ref={r => this.from = r}
|
||||
ref={r => (this.from = r)}
|
||||
defaultValue={from || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -73,7 +73,7 @@ const SMTPConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="smtp-user"
|
||||
type="text"
|
||||
ref={r => this.username = r}
|
||||
ref={r => (this.username = r)}
|
||||
defaultValue={username || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -84,7 +84,7 @@ const SMTPConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="smtp-password"
|
||||
type="password"
|
||||
ref={r => this.password = r}
|
||||
ref={r => (this.password = r)}
|
||||
defaultValue={`${password}`}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -33,7 +33,7 @@ const SensuConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="source"
|
||||
type="text"
|
||||
ref={r => this.source = r}
|
||||
ref={r => (this.source = r)}
|
||||
defaultValue={source || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -44,7 +44,7 @@ const SensuConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="address"
|
||||
type="text"
|
||||
ref={r => this.addr = r}
|
||||
ref={r => (this.addr = r)}
|
||||
defaultValue={addr || ''}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@ const SlackConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={url}
|
||||
id="url"
|
||||
refFunc={r => this.url = r}
|
||||
refFunc={r => (this.url = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -72,7 +72,7 @@ const SlackConfig = React.createClass({
|
|||
id="slack-channel"
|
||||
type="text"
|
||||
placeholder="#alerts"
|
||||
ref={r => this.channel = r}
|
||||
ref={r => (this.channel = r)}
|
||||
defaultValue={channel || ''}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -36,7 +36,7 @@ const TalkConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={url}
|
||||
id="url"
|
||||
refFunc={r => this.url = r}
|
||||
refFunc={r => (this.url = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -46,7 +46,7 @@ const TalkConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="author"
|
||||
type="text"
|
||||
ref={r => this.author = r}
|
||||
ref={r => (this.author = r)}
|
||||
defaultValue={author || ''}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -75,7 +75,7 @@ const TelegramConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={token}
|
||||
id="token"
|
||||
refFunc={r => this.token = r}
|
||||
refFunc={r => (this.token = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -92,7 +92,7 @@ const TelegramConfig = React.createClass({
|
|||
id="chat-id"
|
||||
type="text"
|
||||
placeholder="your-telegram-chat-id"
|
||||
ref={r => this.chatID = r}
|
||||
ref={r => (this.chatID = r)}
|
||||
defaultValue={chatID || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -107,7 +107,7 @@ const TelegramConfig = React.createClass({
|
|||
name="parseMode"
|
||||
value="markdown"
|
||||
defaultChecked={parseMode !== 'HTML'}
|
||||
ref={r => this.parseModeMarkdown = r}
|
||||
ref={r => (this.parseModeMarkdown = r)}
|
||||
/>
|
||||
<label htmlFor="parseModeMarkdown">Markdown</label>
|
||||
</div>
|
||||
|
@ -118,7 +118,7 @@ const TelegramConfig = React.createClass({
|
|||
name="parseMode"
|
||||
value="html"
|
||||
defaultChecked={parseMode === 'HTML'}
|
||||
ref={r => this.parseModeHTML = r}
|
||||
ref={r => (this.parseModeHTML = r)}
|
||||
/>
|
||||
<label htmlFor="parseModeHTML">HTML</label>
|
||||
</div>
|
||||
|
@ -131,7 +131,7 @@ const TelegramConfig = React.createClass({
|
|||
id="disableWebPagePreview"
|
||||
type="checkbox"
|
||||
defaultChecked={disableWebPagePreview}
|
||||
ref={r => this.disableWebPagePreview = r}
|
||||
ref={r => (this.disableWebPagePreview = r)}
|
||||
/>
|
||||
<label htmlFor="disableWebPagePreview">
|
||||
Disable
|
||||
|
@ -151,7 +151,7 @@ const TelegramConfig = React.createClass({
|
|||
id="disableNotification"
|
||||
type="checkbox"
|
||||
defaultChecked={disableNotification}
|
||||
ref={r => this.disableNotification = r}
|
||||
ref={r => (this.disableNotification = r)}
|
||||
/>
|
||||
<label htmlFor="disableNotification">
|
||||
Disable notifications on iOS devices and disable sounds on Android devices. Android users continue to receive notifications.
|
||||
|
|
|
@ -39,7 +39,7 @@ const VictorOpsConfig = React.createClass({
|
|||
<RedactedInput
|
||||
defaultValue={apiKey}
|
||||
id="api-key"
|
||||
refFunc={r => this.apiKey = r}
|
||||
refFunc={r => (this.apiKey = r)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -49,7 +49,7 @@ const VictorOpsConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="routing-key"
|
||||
type="text"
|
||||
ref={r => this.routingKey = r}
|
||||
ref={r => (this.routingKey = r)}
|
||||
defaultValue={routingKey || ''}
|
||||
/>
|
||||
</div>
|
||||
|
@ -60,7 +60,7 @@ const VictorOpsConfig = React.createClass({
|
|||
className="form-control"
|
||||
id="url"
|
||||
type="text"
|
||||
ref={r => this.url = r}
|
||||
ref={r => (this.url = r)}
|
||||
defaultValue={url || ''}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -19,12 +19,32 @@ export const defaultRuleConfigs = {
|
|||
},
|
||||
}
|
||||
|
||||
export const OPERATORS = ['greater than', 'equal to or greater', 'equal to or less than', 'less than', 'equal to', 'not equal to', 'inside range', 'outside range']
|
||||
export const OPERATORS = [
|
||||
'greater than',
|
||||
'equal to or greater',
|
||||
'equal to or less than',
|
||||
'less than',
|
||||
'equal to',
|
||||
'not equal to',
|
||||
'inside range',
|
||||
'outside range',
|
||||
]
|
||||
// export const RELATIONS = ['once', 'more than ', 'less than'];
|
||||
export const PERIODS = ['1m', '5m', '10m', '30m', '1h', '2h', '24h']
|
||||
export const CHANGES = ['change', '% change']
|
||||
export const SHIFTS = ['1m', '5m', '10m', '30m', '1h', '2h', '24h']
|
||||
export const ALERTS = ['alerta', 'hipchat', 'opsgenie', 'pagerduty', 'sensu', 'slack', 'smtp', 'talk', 'telegram', 'victorops']
|
||||
export const ALERTS = [
|
||||
'alerta',
|
||||
'hipchat',
|
||||
'opsgenie',
|
||||
'pagerduty',
|
||||
'sensu',
|
||||
'slack',
|
||||
'smtp',
|
||||
'talk',
|
||||
'telegram',
|
||||
'victorops',
|
||||
]
|
||||
|
||||
export const DEFAULT_RULE_ID = 'DEFAULT_RULE_ID'
|
||||
|
||||
|
@ -32,16 +52,30 @@ export const RULE_MESSAGE_TEMPLATES = {
|
|||
id: {label: '{{.ID}}', text: 'The ID of the alert'},
|
||||
name: {label: '{{.Name}}', text: 'Measurement name'},
|
||||
taskName: {label: '{{.TaskName}}', text: 'The name of the task'},
|
||||
group: {label: '{{.Group}}', text: 'Concatenation of all group-by tags of the form <code>[key=value,]+</code>. If no groupBy is performed equal to literal "nil"'},
|
||||
tags: {label: '{{.Tags}}', text: 'Map of tags. Use <code>{{ index .Tags "key" }}</code> to get a specific tag value'},
|
||||
level: {label: '{{.Level}}', text: 'Alert Level, one of: <code>INFO</code><code>WARNING</code><code>CRITICAL</code>'},
|
||||
fields: {label: '{{ index .Fields "value" }}', text: 'Map of fields. Use <code>{{ index .Fields "key" }}</code> to get a specific field value'},
|
||||
time: {label: '{{.Time}}', text: 'The time of the point that triggered the event'},
|
||||
group: {
|
||||
label: '{{.Group}}',
|
||||
text: 'Concatenation of all group-by tags of the form <code>[key=value,]+</code>. If no groupBy is performed equal to literal "nil"',
|
||||
},
|
||||
tags: {
|
||||
label: '{{.Tags}}',
|
||||
text: 'Map of tags. Use <code>{{ index .Tags "key" }}</code> to get a specific tag value',
|
||||
},
|
||||
level: {
|
||||
label: '{{.Level}}',
|
||||
text: 'Alert Level, one of: <code>INFO</code><code>WARNING</code><code>CRITICAL</code>',
|
||||
},
|
||||
fields: {
|
||||
label: '{{ index .Fields "value" }}',
|
||||
text: 'Map of fields. Use <code>{{ index .Fields "key" }}</code> to get a specific field value',
|
||||
},
|
||||
time: {
|
||||
label: '{{.Time}}',
|
||||
text: 'The time of the point that triggered the event',
|
||||
},
|
||||
}
|
||||
|
||||
export const DEFAULT_ALERTS = ['http', 'tcp', 'exec']
|
||||
|
||||
|
||||
export const DEFAULT_ALERT_LABELS = {
|
||||
http: 'URL:',
|
||||
tcp: 'Address:',
|
||||
|
@ -60,13 +94,19 @@ export const DEFAULT_ALERT_PLACEHOLDERS = {
|
|||
}
|
||||
|
||||
export const ALERT_NODES_ACCESSORS = {
|
||||
http: (rule) => _.get(rule, 'alertNodes[0].args[0]', ''),
|
||||
tcp: (rule) => _.get(rule, 'alertNodes[0].args[0]', ''),
|
||||
exec: (rule) => _.get(rule, 'alertNodes[0].args', []).join(' '),
|
||||
smtp: (rule) => _.get(rule, 'alertNodes[0].args', []).join(' '),
|
||||
slack: (rule) => _.get(rule, 'alertNodes[0].properties[0].args', ''),
|
||||
alerta: (rule) => _.get(rule, 'alertNodes[0].properties', []).reduce((strs, item) => {
|
||||
strs.push(`${item.name}('${item.args.join(' ')}')`)
|
||||
return strs
|
||||
}, ['alerta()']).join('.'),
|
||||
http: rule => _.get(rule, 'alertNodes[0].args[0]', ''),
|
||||
tcp: rule => _.get(rule, 'alertNodes[0].args[0]', ''),
|
||||
exec: rule => _.get(rule, 'alertNodes[0].args', []).join(' '),
|
||||
smtp: rule => _.get(rule, 'alertNodes[0].args', []).join(' '),
|
||||
slack: rule => _.get(rule, 'alertNodes[0].properties[0].args', ''),
|
||||
alerta: rule =>
|
||||
_.get(rule, 'alertNodes[0].properties', [])
|
||||
.reduce(
|
||||
(strs, item) => {
|
||||
strs.push(`${item.name}('${item.args.join(' ')}')`)
|
||||
return strs
|
||||
},
|
||||
['alerta()']
|
||||
)
|
||||
.join('.'),
|
||||
}
|
||||
|
|
|
@ -35,10 +35,13 @@ class KapacitorPage extends Component {
|
|||
return
|
||||
}
|
||||
|
||||
getKapacitor(source, id).then((kapacitor) => {
|
||||
getKapacitor(source, id).then(kapacitor => {
|
||||
this.setState({kapacitor, exists: true}, () => {
|
||||
pingKapacitor(kapacitor).catch(() => {
|
||||
this.props.addFlashMessage({type: 'error', text: 'Could not connect to Kapacitor. Check settings.'})
|
||||
this.props.addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'Could not connect to Kapacitor. Check settings.',
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -47,7 +50,7 @@ class KapacitorPage extends Component {
|
|||
handleInputChange(e) {
|
||||
const {value, name} = e.target
|
||||
|
||||
this.setState((prevState) => {
|
||||
this.setState(prevState => {
|
||||
const update = {[name]: value.trim()}
|
||||
return {kapacitor: {...prevState.kapacitor, ...update}}
|
||||
})
|
||||
|
@ -59,19 +62,29 @@ class KapacitorPage extends Component {
|
|||
const {kapacitor, exists} = this.state
|
||||
|
||||
if (exists) {
|
||||
updateKapacitor(kapacitor).then(() => {
|
||||
addFlashMessage({type: 'success', text: 'Kapacitor Updated!'})
|
||||
}).catch(() => {
|
||||
addFlashMessage({type: 'error', text: 'There was a problem updating the Kapacitor record'})
|
||||
})
|
||||
updateKapacitor(kapacitor)
|
||||
.then(() => {
|
||||
addFlashMessage({type: 'success', text: 'Kapacitor Updated!'})
|
||||
})
|
||||
.catch(() => {
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was a problem updating the Kapacitor record',
|
||||
})
|
||||
})
|
||||
} else {
|
||||
createKapacitor(source, kapacitor).then(({data}) => {
|
||||
// need up update kapacitor with info from server to AlertOutputs
|
||||
this.setState({kapacitor: data, exists: true})
|
||||
addFlashMessage({type: 'success', text: 'Kapacitor Created!'})
|
||||
}).catch(() => {
|
||||
addFlashMessage({type: 'error', text: 'There was a problem creating the Kapacitor record'})
|
||||
})
|
||||
createKapacitor(source, kapacitor)
|
||||
.then(({data}) => {
|
||||
// need up update kapacitor with info from server to AlertOutputs
|
||||
this.setState({kapacitor: data, exists: true})
|
||||
addFlashMessage({type: 'success', text: 'Kapacitor Created!'})
|
||||
})
|
||||
.catch(() => {
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was a problem creating the Kapacitor record',
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -112,11 +125,7 @@ class KapacitorPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
KapacitorPage.propTypes = {
|
||||
addFlashMessage: func,
|
||||
|
|
|
@ -52,31 +52,56 @@ export const KapacitorRulePage = React.createClass({
|
|||
kapacitorActions.loadDefaultRule()
|
||||
}
|
||||
|
||||
getActiveKapacitor(source).then((kapacitor) => {
|
||||
getActiveKapacitor(source).then(kapacitor => {
|
||||
this.setState({kapacitor})
|
||||
getKapacitorConfig(kapacitor).then(({data: {sections}}) => {
|
||||
const enabledAlerts = Object.keys(sections).filter((section) => {
|
||||
return _.get(sections, [section, 'elements', '0', 'options', 'enabled'], false) && ALERTS.includes(section)
|
||||
getKapacitorConfig(kapacitor)
|
||||
.then(({data: {sections}}) => {
|
||||
const enabledAlerts = Object.keys(sections).filter(section => {
|
||||
return (
|
||||
_.get(
|
||||
sections,
|
||||
[section, 'elements', '0', 'options', 'enabled'],
|
||||
false
|
||||
) && ALERTS.includes(section)
|
||||
)
|
||||
})
|
||||
this.setState({enabledAlerts})
|
||||
})
|
||||
.catch(() => {
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: 'There was a problem communicating with Kapacitor',
|
||||
})
|
||||
})
|
||||
.catch(() => {
|
||||
addFlashMessage({
|
||||
type: 'error',
|
||||
text: "We couldn't find a configured Kapacitor for this source",
|
||||
})
|
||||
})
|
||||
this.setState({enabledAlerts})
|
||||
}).catch(() => {
|
||||
addFlashMessage({type: 'error', text: 'There was a problem communicating with Kapacitor'})
|
||||
}).catch(() => {
|
||||
addFlashMessage({type: 'error', text: 'We couldn\'t find a configured Kapacitor for this source'})
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
render() {
|
||||
const {rules, queryConfigs, params, kapacitorActions,
|
||||
source, queryActions, addFlashMessage, router} = this.props
|
||||
const {
|
||||
rules,
|
||||
queryConfigs,
|
||||
params,
|
||||
kapacitorActions,
|
||||
source,
|
||||
queryActions,
|
||||
addFlashMessage,
|
||||
router,
|
||||
} = this.props
|
||||
const {enabledAlerts, kapacitor} = this.state
|
||||
|
||||
const rule = this.isEditing() ? rules[params.ruleID] : rules[DEFAULT_RULE_ID]
|
||||
const rule = this.isEditing()
|
||||
? rules[params.ruleID]
|
||||
: rules[DEFAULT_RULE_ID]
|
||||
const query = rule && queryConfigs[rule.queryID]
|
||||
|
||||
if (!query) {
|
||||
return <div className="page-spinner"></div>
|
||||
return <div className="page-spinner" />
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -18,7 +18,7 @@ class KapacitorRulesPage extends Component {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
getActiveKapacitor(this.props.source).then((kapacitor) => {
|
||||
getActiveKapacitor(this.props.source).then(kapacitor => {
|
||||
if (kapacitor) {
|
||||
this.props.actions.fetchRules(kapacitor)
|
||||
}
|
||||
|
@ -56,12 +56,7 @@ class KapacitorRulesPage extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, shape, string} = PropTypes
|
||||
|
||||
KapacitorRulesPage.propTypes = {
|
||||
source: shape({
|
||||
|
@ -72,12 +67,14 @@ KapacitorRulesPage.propTypes = {
|
|||
kapacitors: string.isRequired,
|
||||
}),
|
||||
}),
|
||||
rules: arrayOf(shape({
|
||||
name: string.isRequired,
|
||||
trigger: string.isRequired,
|
||||
message: string.isRequired,
|
||||
alerts: arrayOf(string.isRequired).isRequired,
|
||||
})).isRequired,
|
||||
rules: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
trigger: string.isRequired,
|
||||
message: string.isRequired,
|
||||
alerts: arrayOf(string.isRequired).isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
actions: shape({
|
||||
fetchRules: func.isRequired,
|
||||
deleteRule: func.isRequired,
|
||||
|
@ -86,13 +83,13 @@ KapacitorRulesPage.propTypes = {
|
|||
addFlashMessage: func,
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
rules: Object.values(state.rules),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
actions: bindActionCreators(kapacitorActionCreators, dispatch),
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@ export const KapacitorTasksPage = React.createClass({
|
|||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
},
|
||||
|
||||
render() {
|
||||
|
@ -26,7 +25,6 @@ export const KapacitorTasksPage = React.createClass({
|
|||
</div>
|
||||
)
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
export default KapacitorTasksPage
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
const telegramChatIDLink = 'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-chat-id'
|
||||
const telegramChatIDLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-chat-id'
|
||||
export const TELEGRAM_CHAT_ID_TIP = `<p>Need help finding your chat id?<br/>Check out <a target='_blank' href='${telegramChatIDLink}'>these steps</a>.</p>`
|
||||
|
||||
const telegramTokenLink = 'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-api-access-token'
|
||||
const telegramTokenLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#telegram-api-access-token'
|
||||
export const TELEGRAM_TOKEN_TIP = `<p>Need help finding your token?<br/>Check out <a target='_blank' href='${telegramTokenLink}'>these steps</a>.</p>`
|
||||
|
||||
const hipchatTokenLink = 'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#hipchat-api-access-token'
|
||||
const hipchatTokenLink =
|
||||
'https://docs.influxdata.com/kapacitor/latest/guides/event-handler-setup/#hipchat-api-access-token'
|
||||
export const HIPCHAT_TOKEN_TIP = `<p>Need help creating a token?<br/>Check out <a href='${hipchatTokenLink}' target='_blank'>these steps</a>.</p>`
|
||||
|
|
|
@ -2,4 +2,9 @@ import KapacitorPage from './containers/KapacitorPage'
|
|||
import KapacitorRulePage from './containers/KapacitorRulePage'
|
||||
import KapacitorRulesPage from './containers/KapacitorRulesPage'
|
||||
import KapacitorTasksPage from './containers/KapacitorTasksPage'
|
||||
export {KapacitorPage, KapacitorRulePage, KapacitorRulesPage, KapacitorTasksPage}
|
||||
export {
|
||||
KapacitorPage,
|
||||
KapacitorRulePage,
|
||||
KapacitorRulesPage,
|
||||
KapacitorTasksPage,
|
||||
}
|
||||
|
|
|
@ -84,9 +84,7 @@ export default function rules(state = {}, action) {
|
|||
alertNodesByType = [
|
||||
{
|
||||
name: alertType,
|
||||
args: [
|
||||
alertNodesText,
|
||||
],
|
||||
args: [alertNodesText],
|
||||
properties: [],
|
||||
},
|
||||
]
|
||||
|
@ -108,9 +106,7 @@ export default function rules(state = {}, action) {
|
|||
properties: [
|
||||
{
|
||||
name: 'channel',
|
||||
args: [
|
||||
alertNodesText,
|
||||
],
|
||||
args: [alertNodesText],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -154,17 +150,23 @@ export default function rules(state = {}, action) {
|
|||
case 'UPDATE_RULE_DETAILS': {
|
||||
const {ruleID, details} = action.payload
|
||||
|
||||
return {...state, ...{
|
||||
[ruleID]: {...state[ruleID], details},
|
||||
}}
|
||||
return {
|
||||
...state,
|
||||
...{
|
||||
[ruleID]: {...state[ruleID], details},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
case 'UPDATE_RULE_STATUS_SUCCESS': {
|
||||
const {ruleID, status} = action.payload
|
||||
|
||||
return {...state, ...{
|
||||
[ruleID]: {...state[ruleID], status},
|
||||
}}
|
||||
return {
|
||||
...state,
|
||||
...{
|
||||
[ruleID]: {...state[ruleID], status},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return state
|
||||
|
|
|
@ -9,16 +9,24 @@ export const loadLocalStorage = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const saveToLocalStorage = ({app: {persisted}, queryConfigs, timeRange, dataExplorer}) => {
|
||||
export const saveToLocalStorage = ({
|
||||
app: {persisted},
|
||||
queryConfigs,
|
||||
timeRange,
|
||||
dataExplorer,
|
||||
}) => {
|
||||
try {
|
||||
const appPersisted = Object.assign({}, {app: {persisted}})
|
||||
|
||||
window.localStorage.setItem('state', JSON.stringify({
|
||||
...appPersisted,
|
||||
queryConfigs,
|
||||
timeRange,
|
||||
dataExplorer,
|
||||
}))
|
||||
window.localStorage.setItem(
|
||||
'state',
|
||||
JSON.stringify({
|
||||
...appPersisted,
|
||||
queryConfigs,
|
||||
timeRange,
|
||||
dataExplorer,
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
console.error('Unable to save data explorer: ', JSON.parse(err)) // eslint-disable-line no-console
|
||||
}
|
||||
|
|
|
@ -9,12 +9,15 @@ export const disablePresentationMode = () => ({
|
|||
type: 'DISABLE_PRESENTATION_MODE',
|
||||
})
|
||||
|
||||
export const delayEnablePresentationMode = () => (dispatch) => {
|
||||
setTimeout(() => dispatch(enablePresentationMode()), PRESENTATION_MODE_ANIMATION_DELAY)
|
||||
export const delayEnablePresentationMode = () => dispatch => {
|
||||
setTimeout(
|
||||
() => dispatch(enablePresentationMode()),
|
||||
PRESENTATION_MODE_ANIMATION_DELAY
|
||||
)
|
||||
}
|
||||
|
||||
// persistent state action creators
|
||||
export const setAutoRefresh = (milliseconds) => ({
|
||||
export const setAutoRefresh = milliseconds => ({
|
||||
type: 'SET_AUTOREFRESH',
|
||||
payload: {
|
||||
milliseconds,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export const authExpired = (auth) => ({
|
||||
export const authExpired = auth => ({
|
||||
type: 'AUTH_EXPIRED',
|
||||
payload: {
|
||||
auth,
|
||||
|
@ -9,7 +9,7 @@ export const authRequested = () => ({
|
|||
type: 'AUTH_REQUESTED',
|
||||
})
|
||||
|
||||
export const authReceived = (auth) => ({
|
||||
export const authReceived = auth => ({
|
||||
type: 'AUTH_RECEIVED',
|
||||
payload: {
|
||||
auth,
|
||||
|
@ -20,7 +20,7 @@ export const meRequested = () => ({
|
|||
type: 'ME_REQUESTED',
|
||||
})
|
||||
|
||||
export const meReceived = (me) => ({
|
||||
export const meReceived = me => ({
|
||||
type: 'ME_RECEIVED',
|
||||
payload: {
|
||||
me,
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import {deleteSource,
|
||||
import {
|
||||
deleteSource,
|
||||
getSources,
|
||||
getKapacitors as getKapacitorsAJAX,
|
||||
updateKapacitor as updateKapacitorAJAX,
|
||||
} from 'src/shared/apis'
|
||||
import {publishNotification} from './notifications'
|
||||
|
||||
export const loadSources = (sources) => ({
|
||||
export const loadSources = sources => ({
|
||||
type: 'LOAD_SOURCES',
|
||||
payload: {
|
||||
sources,
|
||||
},
|
||||
})
|
||||
|
||||
export const updateSource = (source) => ({
|
||||
export const updateSource = source => ({
|
||||
type: 'SOURCE_UPDATED',
|
||||
payload: {
|
||||
source,
|
||||
},
|
||||
})
|
||||
|
||||
export const addSource = (source) => ({
|
||||
export const addSource = source => ({
|
||||
type: 'SOURCE_ADDED',
|
||||
payload: {
|
||||
source,
|
||||
|
@ -34,7 +35,7 @@ export const fetchKapacitors = (source, kapacitors) => ({
|
|||
},
|
||||
})
|
||||
|
||||
export const setActiveKapacitor = (kapacitor) => ({
|
||||
export const setActiveKapacitor = kapacitor => ({
|
||||
type: 'SET_ACTIVE_KAPACITOR',
|
||||
payload: {
|
||||
kapacitor,
|
||||
|
@ -43,35 +44,43 @@ export const setActiveKapacitor = (kapacitor) => ({
|
|||
|
||||
// Async action creators
|
||||
|
||||
export const removeAndLoadSources = (source) => async (dispatch) => {
|
||||
export const removeAndLoadSources = source => async dispatch => {
|
||||
try {
|
||||
try {
|
||||
await deleteSource(source)
|
||||
} catch (err) {
|
||||
// A 404 means that either a concurrent write occurred or the source
|
||||
// passed to this action creator doesn't exist (or is undefined)
|
||||
if (err.status !== 404) { // eslint-disable-line no-magic-numbers
|
||||
throw (err)
|
||||
if (err.status !== 404) {
|
||||
// eslint-disable-line no-magic-numbers
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const {data: {sources: newSources}} = await getSources()
|
||||
dispatch(loadSources(newSources))
|
||||
} catch (err) {
|
||||
dispatch(publishNotification('error', 'Internal Server Error. Check API Logs'))
|
||||
dispatch(
|
||||
publishNotification('error', 'Internal Server Error. Check API Logs')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchKapacitorsAsync = (source) => async (dispatch) => {
|
||||
export const fetchKapacitorsAsync = source => async dispatch => {
|
||||
try {
|
||||
const {data} = await getKapacitorsAJAX(source)
|
||||
dispatch(fetchKapacitors(source, data.kapacitors))
|
||||
} catch (err) {
|
||||
dispatch(publishNotification('error', `Internal Server Error. Could not retrieve kapacitors for source ${source.id}.`))
|
||||
dispatch(
|
||||
publishNotification(
|
||||
'error',
|
||||
`Internal Server Error. Could not retrieve kapacitors for source ${source.id}.`
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const setActiveKapacitorAsync = (kapacitor) => async (dispatch) => {
|
||||
export const setActiveKapacitorAsync = kapacitor => async dispatch => {
|
||||
// eagerly update the redux state
|
||||
dispatch(setActiveKapacitor(kapacitor))
|
||||
const kapacitorPost = {...kapacitor, active: true}
|
||||
|
|
|
@ -72,12 +72,12 @@ export function getActiveKapacitor(source) {
|
|||
url: source.links.kapacitors,
|
||||
method: 'GET',
|
||||
}).then(({data}) => {
|
||||
const activeKapacitor = data.kapacitors.find((k) => k.active)
|
||||
const activeKapacitor = data.kapacitors.find(k => k.active)
|
||||
return activeKapacitor || data.kapacitors[0]
|
||||
})
|
||||
}
|
||||
|
||||
export const getKapacitors = async (source) => {
|
||||
export const getKapacitors = async source => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
|
@ -89,7 +89,10 @@ export const getKapacitors = async (source) => {
|
|||
}
|
||||
}
|
||||
|
||||
export function createKapacitor(source, {url, name = 'My Kapacitor', username, password}) {
|
||||
export function createKapacitor(
|
||||
source,
|
||||
{url, name = 'My Kapacitor', username, password}
|
||||
) {
|
||||
return AJAX({
|
||||
url: source.links.kapacitors,
|
||||
method: 'POST',
|
||||
|
@ -102,7 +105,14 @@ export function createKapacitor(source, {url, name = 'My Kapacitor', username, p
|
|||
})
|
||||
}
|
||||
|
||||
export function updateKapacitor({links, url, name = 'My Kapacitor', username, password, active}) {
|
||||
export function updateKapacitor({
|
||||
links,
|
||||
url,
|
||||
name = 'My Kapacitor',
|
||||
username,
|
||||
password,
|
||||
active,
|
||||
}) {
|
||||
return AJAX({
|
||||
url: links.self,
|
||||
method: 'PATCH',
|
||||
|
@ -141,9 +151,18 @@ export function updateKapacitorConfigSection(kapacitor, section, properties) {
|
|||
}
|
||||
|
||||
export function testAlertOutput(kapacitor, outputName, properties) {
|
||||
return kapacitorProxy(kapacitor, 'GET', '/kapacitor/v1/service-tests').then(({data: {services}}) => {
|
||||
return kapacitorProxy(
|
||||
kapacitor,
|
||||
'GET',
|
||||
'/kapacitor/v1/service-tests'
|
||||
).then(({data: {services}}) => {
|
||||
const service = services.find(s => s.name === outputName)
|
||||
return kapacitorProxy(kapacitor, 'POST', service.link.href, Object.assign({}, service.options, properties))
|
||||
return kapacitorProxy(
|
||||
kapacitor,
|
||||
'POST',
|
||||
service.link.href,
|
||||
Object.assign({}, service.options, properties)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -158,11 +177,15 @@ export function createKapacitorTask(kapacitor, id, type, dbrps, script) {
|
|||
}
|
||||
|
||||
export function enableKapacitorTask(kapacitor, id) {
|
||||
return kapacitorProxy(kapacitor, 'PATCH', `/kapacitor/v1/tasks/${id}`, {status: 'enabled'})
|
||||
return kapacitorProxy(kapacitor, 'PATCH', `/kapacitor/v1/tasks/${id}`, {
|
||||
status: 'enabled',
|
||||
})
|
||||
}
|
||||
|
||||
export function disableKapacitorTask(kapacitor, id) {
|
||||
return kapacitorProxy(kapacitor, 'PATCH', `/kapacitor/v1/tasks/${id}`, {status: 'disabled'})
|
||||
return kapacitorProxy(kapacitor, 'PATCH', `/kapacitor/v1/tasks/${id}`, {
|
||||
status: 'disabled',
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteKapacitorTask(kapacitor, id) {
|
||||
|
@ -180,8 +203,9 @@ export function kapacitorProxy(kapacitor, method, path, body) {
|
|||
})
|
||||
}
|
||||
|
||||
export const getQueryConfig = (url, queries) => AJAX({
|
||||
url,
|
||||
method: 'POST',
|
||||
data: {queries},
|
||||
})
|
||||
export const getQueryConfig = (url, queries) =>
|
||||
AJAX({
|
||||
url,
|
||||
method: 'POST',
|
||||
data: {queries},
|
||||
})
|
||||
|
|
|
@ -4,10 +4,7 @@ import OnClickOutside from 'shared/components/OnClickOutside'
|
|||
|
||||
import autoRefreshItems from 'hson!../data/autoRefreshes.hson'
|
||||
|
||||
const {
|
||||
number,
|
||||
func,
|
||||
} = PropTypes
|
||||
const {number, func} = PropTypes
|
||||
|
||||
const AutoRefreshDropdown = React.createClass({
|
||||
autobind: false,
|
||||
|
@ -24,7 +21,7 @@ const AutoRefreshDropdown = React.createClass({
|
|||
},
|
||||
|
||||
findAutoRefreshItem(milliseconds) {
|
||||
return autoRefreshItems.find((values) => values.milliseconds === milliseconds)
|
||||
return autoRefreshItems.find(values => values.milliseconds === milliseconds)
|
||||
},
|
||||
|
||||
handleClickOutside() {
|
||||
|
@ -48,17 +45,28 @@ const AutoRefreshDropdown = React.createClass({
|
|||
|
||||
return (
|
||||
<div className="dropdown dropdown-160">
|
||||
<div className="btn btn-sm btn-info dropdown-toggle" onClick={() => self.toggleMenu()}>
|
||||
<span className={classnames('icon', +milliseconds > 0 ? 'refresh' : 'pause')}></span>
|
||||
<div
|
||||
className="btn btn-sm btn-info dropdown-toggle"
|
||||
onClick={() => self.toggleMenu()}
|
||||
>
|
||||
<span
|
||||
className={classnames(
|
||||
'icon',
|
||||
+milliseconds > 0 ? 'refresh' : 'pause'
|
||||
)}
|
||||
/>
|
||||
<span className="selected-time-range">{inputValue}</span>
|
||||
<span className="caret" />
|
||||
</div>
|
||||
<ul className={classnames('dropdown-menu', {show: isOpen})}>
|
||||
<li className="dropdown-header">AutoRefresh Interval</li>
|
||||
{autoRefreshItems.map((item) => {
|
||||
{autoRefreshItems.map(item => {
|
||||
return (
|
||||
<li key={item.menuOption}>
|
||||
<a href="#" onClick={() => self.handleSelection(item.milliseconds)}>
|
||||
<a
|
||||
href="#"
|
||||
onClick={() => self.handleSelection(item.milliseconds)}
|
||||
>
|
||||
{item.menuOption}
|
||||
</a>
|
||||
</li>
|
||||
|
|
|
@ -2,34 +2,20 @@ import React, {PropTypes} from 'react'
|
|||
|
||||
const ConfirmButtons = ({onConfirm, item, onCancel}) => (
|
||||
<div className="confirm-buttons">
|
||||
<button
|
||||
className="btn btn-xs btn-info"
|
||||
onClick={() => onCancel(item)}
|
||||
>
|
||||
<span className="icon remove"></span>
|
||||
<button className="btn btn-xs btn-info" onClick={() => onCancel(item)}>
|
||||
<span className="icon remove" />
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-xs btn-success"
|
||||
onClick={() => onConfirm(item)}
|
||||
>
|
||||
<span className="icon checkmark"></span>
|
||||
<button className="btn btn-xs btn-success" onClick={() => onConfirm(item)}>
|
||||
<span className="icon checkmark" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
func,
|
||||
oneOfType,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, oneOfType, shape, string} = PropTypes
|
||||
|
||||
ConfirmButtons.propTypes = {
|
||||
onConfirm: func.isRequired,
|
||||
item: oneOfType([
|
||||
shape(),
|
||||
string,
|
||||
]),
|
||||
item: oneOfType([shape(), string]),
|
||||
onCancel: func.isRequired,
|
||||
}
|
||||
|
||||
|
|
|
@ -45,18 +45,29 @@ class CustomTimeRange extends Component {
|
|||
const {isVisible, onToggle, timeRange: {upper, lower}} = this.props
|
||||
|
||||
return (
|
||||
<div className={classNames('custom-time-range', {show: isVisible})} style={{display: 'flex'}}>
|
||||
<button className="btn btn-sm btn-info custom-time-range--btn" onClick={onToggle}>
|
||||
<span className="icon clock"></span>
|
||||
<div
|
||||
className={classNames('custom-time-range', {show: isVisible})}
|
||||
style={{display: 'flex'}}
|
||||
>
|
||||
<button
|
||||
className="btn btn-sm btn-info custom-time-range--btn"
|
||||
onClick={onToggle}
|
||||
>
|
||||
<span className="icon clock" />
|
||||
{`${moment(lower).format('MMM Do HH:mm')} — ${moment(upper).format('MMM Do HH:mm')}`}
|
||||
<span className="caret"></span>
|
||||
<span className="caret" />
|
||||
</button>
|
||||
<div className="custom-time--container">
|
||||
<div className="custom-time--dates">
|
||||
<div className="custom-time--lower" ref={(r) => this.lower = r} />
|
||||
<div className="custom-time--upper" ref={(r) => this.upper = r} />
|
||||
<div className="custom-time--lower" ref={r => (this.lower = r)} />
|
||||
<div className="custom-time--upper" ref={r => (this.upper = r)} />
|
||||
</div>
|
||||
<div
|
||||
className="custom-time--apply btn btn-sm btn-primary"
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
Apply
|
||||
</div>
|
||||
<div className="custom-time--apply btn btn-sm btn-primary" onClick={this.handleClick}>Apply</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -91,12 +102,7 @@ class CustomTimeRange extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
bool,
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {bool, func, shape, string} = PropTypes
|
||||
|
||||
CustomTimeRange.propTypes = {
|
||||
onApplyTimeRange: func.isRequired,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import DeleteConfirmButtons from 'shared/components/DeleteConfirmButtons'
|
||||
|
||||
const DeleteConfirmTableCell = (props) => (
|
||||
const DeleteConfirmTableCell = props => (
|
||||
<td className="text-right" style={{width: '85px'}}>
|
||||
<DeleteConfirmButtons {...props} />
|
||||
</td>
|
||||
|
|
|
@ -3,14 +3,7 @@ import React, {PropTypes} from 'react'
|
|||
import Dygraph from '../../external/dygraph'
|
||||
import getRange from 'src/shared/parsing/getRangeForDygraph'
|
||||
|
||||
const {
|
||||
array,
|
||||
arrayOf,
|
||||
number,
|
||||
bool,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {array, arrayOf, number, bool, shape, string} = PropTypes
|
||||
|
||||
const LINE_COLORS = [
|
||||
'#00C9FF',
|
||||
|
@ -112,12 +105,12 @@ export default React.createClass({
|
|||
const legendRect = legendContainerNode.getBoundingClientRect()
|
||||
const graphWidth = graphRect.width + 32 // Factoring in padding from parent
|
||||
const legendWidth = legendRect.width
|
||||
const legendMaxLeft = graphWidth - (legendWidth / 2)
|
||||
const trueGraphX = (e.pageX - graphRect.left)
|
||||
const legendMaxLeft = graphWidth - legendWidth / 2
|
||||
const trueGraphX = e.pageX - graphRect.left
|
||||
const legendTop = graphRect.height + 0
|
||||
let legendLeft = trueGraphX
|
||||
// Enforcing max & min legend offsets
|
||||
if (trueGraphX < (legendWidth / 2)) {
|
||||
if (trueGraphX < legendWidth / 2) {
|
||||
legendLeft = legendWidth / 2
|
||||
} else if (trueGraphX > legendMaxLeft) {
|
||||
legendLeft = legendMaxLeft
|
||||
|
@ -159,7 +152,9 @@ export default React.createClass({
|
|||
componentDidUpdate() {
|
||||
const dygraph = this.dygraph
|
||||
if (!dygraph) {
|
||||
throw new Error('Dygraph not configured in time; this should not be possible!')
|
||||
throw new Error(
|
||||
'Dygraph not configured in time; this should not be possible!'
|
||||
)
|
||||
}
|
||||
|
||||
const timeSeries = this.getTimeSeries()
|
||||
|
|
|
@ -3,14 +3,26 @@ import ReactTooltip from 'react-tooltip'
|
|||
|
||||
const GraphTips = React.createClass({
|
||||
render() {
|
||||
const graphTipsText = '<p><b>Graph Tips:</b><br/><br/><code>Click + Drag</code> Zoom in (X or Y)</p><p><code>Shift + Click</code> Pan Graph Window</p><p><code>Double Click</code> Reset Graph Window</p>'
|
||||
const graphTipsText =
|
||||
'<p><b>Graph Tips:</b><br/><br/><code>Click + Drag</code> Zoom in (X or Y)</p><p><code>Shift + Click</code> Pan Graph Window</p><p><code>Double Click</code> Reset Graph Window</p>'
|
||||
return (
|
||||
<div className="graph-tips" data-for="graph-tips-tooltip" data-tip={graphTipsText}>
|
||||
<div
|
||||
className="graph-tips"
|
||||
data-for="graph-tips-tooltip"
|
||||
data-tip={graphTipsText}
|
||||
>
|
||||
<span>?</span>
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default GraphTips
|
||||
export default GraphTips
|
||||
|
|
|
@ -13,14 +13,7 @@ const GridLayout = WidthProvider(ReactGridLayout)
|
|||
const RefreshingLineGraph = AutoRefresh(LineGraph)
|
||||
const RefreshingSingleStat = AutoRefresh(SingleStat)
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
export const LayoutRenderer = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -60,7 +53,7 @@ export const LayoutRenderer = React.createClass({
|
|||
|
||||
buildQueryForOldQuerySchema(q) {
|
||||
const {timeRange: {lower}, host} = this.props
|
||||
const {defaultGroupBy} = timeRanges.find((range) => range.lower === lower)
|
||||
const {defaultGroupBy} = timeRanges.find(range => range.lower === lower)
|
||||
const {wheres, groupbys} = q
|
||||
|
||||
let text = q.text
|
||||
|
@ -76,7 +69,7 @@ export const LayoutRenderer = React.createClass({
|
|||
}
|
||||
|
||||
if (groupbys) {
|
||||
if (groupbys.find((g) => g.includes('time'))) {
|
||||
if (groupbys.find(g => g.includes('time'))) {
|
||||
text += ` group by ${groupbys.join(',')}`
|
||||
} else if (groupbys.length > 0) {
|
||||
text += ` group by time(${defaultGroupBy}),${groupbys.join(',')}`
|
||||
|
@ -120,10 +113,20 @@ export const LayoutRenderer = React.createClass({
|
|||
},
|
||||
|
||||
generateVisualizations() {
|
||||
const {timeRange, source, cells, onEditCell, onRenameCell, onUpdateCell, onDeleteCell, onSummonOverlayTechnologies, shouldNotBeEditable} = this.props
|
||||
const {
|
||||
timeRange,
|
||||
source,
|
||||
cells,
|
||||
onEditCell,
|
||||
onRenameCell,
|
||||
onUpdateCell,
|
||||
onDeleteCell,
|
||||
onSummonOverlayTechnologies,
|
||||
shouldNotBeEditable,
|
||||
} = this.props
|
||||
|
||||
return cells.map((cell) => {
|
||||
const queries = cell.queries.map((query) => {
|
||||
return cells.map(cell => {
|
||||
const queries = cell.queries.map(query => {
|
||||
// TODO: Canned dashboards (and possibly Kubernetes dashboard) 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
|
||||
|
@ -131,7 +134,8 @@ export const LayoutRenderer = React.createClass({
|
|||
let queryText
|
||||
if (query.queryConfig) {
|
||||
const {queryConfig: {rawText}} = query
|
||||
queryText = rawText || buildInfluxQLQuery(timeRange, query.queryConfig)
|
||||
queryText =
|
||||
rawText || buildInfluxQLQuery(timeRange, query.queryConfig)
|
||||
} else {
|
||||
queryText = this.buildQueryForOldQuerySchema(query)
|
||||
}
|
||||
|
@ -167,8 +171,8 @@ export const LayoutRenderer = React.createClass({
|
|||
return
|
||||
}
|
||||
|
||||
const newCells = this.props.cells.map((cell) => {
|
||||
const l = layout.find((ly) => ly.i === cell.i)
|
||||
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}
|
||||
})
|
||||
|
@ -199,10 +203,9 @@ export const LayoutRenderer = React.createClass({
|
|||
)
|
||||
},
|
||||
|
||||
|
||||
triggerWindowResize() {
|
||||
// Hack to get dygraphs to fit properly during and after resize (dispatchEvent is a global method on window).
|
||||
const evt = document.createEvent('CustomEvent') // MUST be 'CustomEvent'
|
||||
const evt = document.createEvent('CustomEvent') // MUST be 'CustomEvent'
|
||||
evt.initCustomEvent('resize', false, false, null)
|
||||
dispatchEvent(evt)
|
||||
},
|
||||
|
|
|
@ -7,15 +7,7 @@ import _ from 'lodash'
|
|||
import timeSeriesToDygraph from 'utils/timeSeriesToDygraph'
|
||||
import lastValues from 'src/shared/parsing/lastValues'
|
||||
|
||||
const {
|
||||
array,
|
||||
arrayOf,
|
||||
bool,
|
||||
func,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {array, arrayOf, bool, func, number, shape, string} = PropTypes
|
||||
|
||||
export default React.createClass({
|
||||
displayName: 'LineGraph',
|
||||
|
@ -56,18 +48,43 @@ export default React.createClass({
|
|||
|
||||
componentWillMount() {
|
||||
const {data, activeQueryIndex, isInDataExplorer} = this.props
|
||||
this._timeSeries = timeSeriesToDygraph(data, activeQueryIndex, isInDataExplorer)
|
||||
this._timeSeries = timeSeriesToDygraph(
|
||||
data,
|
||||
activeQueryIndex,
|
||||
isInDataExplorer
|
||||
)
|
||||
},
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
const {data, activeQueryIndex} = this.props
|
||||
if (data !== nextProps.data || activeQueryIndex !== nextProps.activeQueryIndex) {
|
||||
this._timeSeries = timeSeriesToDygraph(nextProps.data, nextProps.activeQueryIndex, nextProps.isInDataExplorer)
|
||||
if (
|
||||
data !== nextProps.data ||
|
||||
activeQueryIndex !== nextProps.activeQueryIndex
|
||||
) {
|
||||
this._timeSeries = timeSeriesToDygraph(
|
||||
nextProps.data,
|
||||
nextProps.activeQueryIndex,
|
||||
nextProps.isInDataExplorer
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
const {data, ranges, isFetchingInitially, isRefreshing, isGraphFilled, overrideLineColors, title, underlayCallback, queries, showSingleStat, displayOptions, ruleValues, isInDataExplorer} = this.props
|
||||
const {
|
||||
data,
|
||||
ranges,
|
||||
isFetchingInitially,
|
||||
isRefreshing,
|
||||
isGraphFilled,
|
||||
overrideLineColors,
|
||||
title,
|
||||
underlayCallback,
|
||||
queries,
|
||||
showSingleStat,
|
||||
displayOptions,
|
||||
ruleValues,
|
||||
isInDataExplorer,
|
||||
} = this.props
|
||||
const {labels, timeSeries, dygraphSeries} = this._timeSeries
|
||||
|
||||
// If data for this graph is being fetched for the first time, show a graph-wide spinner.
|
||||
|
@ -79,21 +96,25 @@ export default React.createClass({
|
|||
)
|
||||
}
|
||||
|
||||
const options = Object.assign({}, {
|
||||
labels,
|
||||
connectSeparatedPoints: true,
|
||||
labelsKMB: true,
|
||||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
title,
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
axisLabelWidth: 38,
|
||||
drawAxesAtZero: true,
|
||||
underlayCallback,
|
||||
ylabel: _.get(queries, ['0', 'label'], ''),
|
||||
y2label: _.get(queries, ['1', 'label'], ''),
|
||||
}, displayOptions)
|
||||
const options = Object.assign(
|
||||
{},
|
||||
{
|
||||
labels,
|
||||
connectSeparatedPoints: true,
|
||||
labelsKMB: true,
|
||||
axisLineColor: '#383846',
|
||||
gridLineColor: '#383846',
|
||||
title,
|
||||
rightGap: 0,
|
||||
yRangePad: 10,
|
||||
axisLabelWidth: 38,
|
||||
drawAxesAtZero: true,
|
||||
underlayCallback,
|
||||
ylabel: _.get(queries, ['0', 'label'], ''),
|
||||
y2label: _.get(queries, ['1', 'label'], ''),
|
||||
},
|
||||
displayOptions
|
||||
)
|
||||
|
||||
let roundedValue
|
||||
if (showSingleStat) {
|
||||
|
@ -105,7 +126,9 @@ export default React.createClass({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={classNames('dygraph', {'graph--hasYLabel': !!(options.ylabel || options.y2label)})}
|
||||
className={classNames('dygraph', {
|
||||
'graph--hasYLabel': !!(options.ylabel || options.y2label),
|
||||
})}
|
||||
style={{height: '100%'}}
|
||||
>
|
||||
{isRefreshing ? this.renderSpinner() : null}
|
||||
|
@ -121,7 +144,9 @@ export default React.createClass({
|
|||
ruleValues={ruleValues}
|
||||
legendOnBottom={isInDataExplorer}
|
||||
/>
|
||||
{showSingleStat ? <div className="graph-single-stat single-stat">{roundedValue}</div> : null}
|
||||
{showSingleStat
|
||||
? <div className="graph-single-stat single-stat">{roundedValue}</div>
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
@ -129,9 +154,9 @@ export default React.createClass({
|
|||
renderSpinner() {
|
||||
return (
|
||||
<div className="graph-panel__refreshing">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
|
|
|
@ -2,15 +2,13 @@ import React, {PropTypes} from 'react'
|
|||
|
||||
const LoadingDots = ({className}) => (
|
||||
<div className={`loading-dots ${className}`}>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
)
|
||||
|
||||
const {
|
||||
string,
|
||||
} = PropTypes
|
||||
const {string} = PropTypes
|
||||
|
||||
LoadingDots.propTypes = {
|
||||
className: string,
|
||||
|
|
|
@ -28,7 +28,9 @@ export default React.createClass({
|
|||
|
||||
render() {
|
||||
const results = timeSeriesToDygraph(this.props.data)
|
||||
const {fields, timeSeries} = this.props.options.combineSeries ? this.combineSeries(results) : results
|
||||
const {fields, timeSeries} = this.props.options.combineSeries
|
||||
? this.combineSeries(results)
|
||||
: results
|
||||
|
||||
if (!timeSeries.length) {
|
||||
return null
|
||||
|
@ -79,7 +81,13 @@ export default React.createClass({
|
|||
|
||||
return (
|
||||
<div className="cluster-stat">
|
||||
<Dygraph containerStyle={{width: '100%', height: '30px'}} timeSeries={timeSeries} fields={fields} options={options} yRange={this.props.yRange} />
|
||||
<Dygraph
|
||||
containerStyle={{width: '100%', height: '30px'}}
|
||||
timeSeries={timeSeries}
|
||||
fields={fields}
|
||||
options={options}
|
||||
yRange={this.props.yRange}
|
||||
/>
|
||||
{statText}
|
||||
</div>
|
||||
)
|
||||
|
@ -94,23 +102,25 @@ export default React.createClass({
|
|||
*/
|
||||
combineSeries(results) {
|
||||
const fields = results.fields.slice(0, 2) // Hack, but good enough for now for the sparklines (which have no labels).
|
||||
const timeSeries = results.timeSeries.filter((point) => {
|
||||
// Filter out any points that don't report results for *all* of the series
|
||||
// we're trying to combine..
|
||||
//
|
||||
// e.g. [<timestamp>, null, null, 5] would be removed.
|
||||
//
|
||||
// We use `combineSeries` when we want to combine the values for multiple series
|
||||
// into a single series. It makes sense to only report points where all
|
||||
// series are represented, so we can accurately take the sum.
|
||||
return point.slice(1).every((v) => v !== null)
|
||||
}).map((point) => {
|
||||
const timestamp = point[0]
|
||||
const total = point.slice(1).reduce((sum, n) => {
|
||||
return n ? sum + n : sum
|
||||
}, 0)
|
||||
return [timestamp, total]
|
||||
})
|
||||
const timeSeries = results.timeSeries
|
||||
.filter(point => {
|
||||
// Filter out any points that don't report results for *all* of the series
|
||||
// we're trying to combine..
|
||||
//
|
||||
// e.g. [<timestamp>, null, null, 5] would be removed.
|
||||
//
|
||||
// We use `combineSeries` when we want to combine the values for multiple series
|
||||
// into a single series. It makes sense to only report points where all
|
||||
// series are represented, so we can accurately take the sum.
|
||||
return point.slice(1).every(v => v !== null)
|
||||
})
|
||||
.map(point => {
|
||||
const timestamp = point[0]
|
||||
const total = point.slice(1).reduce((sum, n) => {
|
||||
return n ? sum + n : sum
|
||||
}, 0)
|
||||
return [timestamp, total]
|
||||
})
|
||||
return {fields, timeSeries}
|
||||
},
|
||||
})
|
||||
|
|
|
@ -7,7 +7,7 @@ const labelText = ({localSelectedItems, isOpen, label}) => {
|
|||
if (label) {
|
||||
return label
|
||||
} else if (localSelectedItems.length) {
|
||||
return localSelectedItems.map((s) => s).join(', ')
|
||||
return localSelectedItems.map(s => s).join(', ')
|
||||
}
|
||||
|
||||
// TODO: be smarter about the text displayed here
|
||||
|
@ -46,7 +46,7 @@ class MultiSelectDropdown extends Component {
|
|||
|
||||
let nextItems
|
||||
if (this.isSelected(item)) {
|
||||
nextItems = localSelectedItems.filter((i) => i !== item)
|
||||
nextItems = localSelectedItems.filter(i => i !== item)
|
||||
} else {
|
||||
nextItems = localSelectedItems.concat(item)
|
||||
}
|
||||
|
@ -70,14 +70,18 @@ class MultiSelectDropdown extends Component {
|
|||
const {label} = this.props
|
||||
|
||||
return (
|
||||
<div className={classNames('dropdown multi-select-dropdown', {open: isOpen})}>
|
||||
<div onClick={::this.toggleMenu} className="btn btn-xs btn-info dropdown-toggle" type="button">
|
||||
<div
|
||||
className={classNames('dropdown multi-select-dropdown', {open: isOpen})}
|
||||
>
|
||||
<div
|
||||
onClick={::this.toggleMenu}
|
||||
className="btn btn-xs btn-info dropdown-toggle"
|
||||
type="button"
|
||||
>
|
||||
<div className="multi-select-dropdown__label">
|
||||
{
|
||||
labelText({localSelectedItems, isOpen, label})
|
||||
}
|
||||
{labelText({localSelectedItems, isOpen, label})}
|
||||
</div>
|
||||
<span className="caret"></span>
|
||||
<span className="caret" />
|
||||
</div>
|
||||
{this.renderMenu()}
|
||||
</div>
|
||||
|
@ -89,15 +93,24 @@ class MultiSelectDropdown extends Component {
|
|||
|
||||
return (
|
||||
<div className="dropdown-options">
|
||||
<div className="multi-select-dropdown__apply" onClick={this.onApplyFunctions} style={{listStyle: 'none'}}>
|
||||
<div
|
||||
className="multi-select-dropdown__apply"
|
||||
onClick={this.onApplyFunctions}
|
||||
style={{listStyle: 'none'}}
|
||||
>
|
||||
<div className="btn btn-xs btn-info btn-block">Apply</div>
|
||||
</div>
|
||||
<ul className="dropdown-menu multi-select-dropdown__menu" aria-labelledby="dropdownMenu1">
|
||||
<ul
|
||||
className="dropdown-menu multi-select-dropdown__menu"
|
||||
aria-labelledby="dropdownMenu1"
|
||||
>
|
||||
{items.map((listItem, i) => {
|
||||
return (
|
||||
<li
|
||||
key={i}
|
||||
className={classNames('multi-select-dropdown__item', {active: this.isSelected(listItem)})}
|
||||
className={classNames('multi-select-dropdown__item', {
|
||||
active: this.isSelected(listItem),
|
||||
})}
|
||||
onClick={_.wrap(listItem, this.onSelect)}
|
||||
>
|
||||
<a href="#">{listItem}</a>
|
||||
|
@ -110,11 +123,7 @@ class MultiSelectDropdown extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
arrayOf,
|
||||
func,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {arrayOf, func, string} = PropTypes
|
||||
|
||||
MultiSelectDropdown.propTypes = {
|
||||
onApply: func.isRequired,
|
||||
|
|
|
@ -2,14 +2,7 @@ import React, {PropTypes} from 'react'
|
|||
import classnames from 'classnames'
|
||||
import OnClickOutside from 'react-onclickoutside'
|
||||
|
||||
const {
|
||||
bool,
|
||||
func,
|
||||
node,
|
||||
number,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {bool, func, node, number, shape, string} = PropTypes
|
||||
|
||||
const NameableGraph = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -49,12 +42,7 @@ const NameableGraph = React.createClass({
|
|||
render() {
|
||||
const {
|
||||
cell,
|
||||
cell: {
|
||||
x,
|
||||
y,
|
||||
name,
|
||||
isEditing,
|
||||
},
|
||||
cell: {x, y, name, isEditing},
|
||||
onEditCell,
|
||||
onRenameCell,
|
||||
onUpdateCell,
|
||||
|
@ -76,7 +64,7 @@ const NameableGraph = React.createClass({
|
|||
autoFocus={true}
|
||||
onChange={onRenameCell(x, y)}
|
||||
onBlur={onUpdateCell(cell)}
|
||||
onKeyUp={(evt) => {
|
||||
onKeyUp={evt => {
|
||||
if (evt.key === 'Enter') {
|
||||
onUpdateCell(cell)()
|
||||
}
|
||||
|
@ -87,7 +75,7 @@ const NameableGraph = React.createClass({
|
|||
/>
|
||||
)
|
||||
} else {
|
||||
nameOrField = (<span className="dash-graph--name">{name}</span>)
|
||||
nameOrField = <span className="dash-graph--name">{name}</span>
|
||||
}
|
||||
|
||||
let onStartRenaming
|
||||
|
@ -101,20 +89,24 @@ const NameableGraph = React.createClass({
|
|||
|
||||
return (
|
||||
<div className="dash-graph">
|
||||
<div className={classnames('dash-graph--heading', {'dash-graph--heading-draggable': !shouldNotBeEditable})}>{nameOrField}</div>
|
||||
{
|
||||
shouldNotBeEditable ?
|
||||
null :
|
||||
<ContextMenu
|
||||
isOpen={this.state.isMenuOpen}
|
||||
toggleMenu={this.toggleMenu}
|
||||
onEdit={onSummonOverlayTechnologies}
|
||||
onRename={onStartRenaming}
|
||||
onDelete={onDeleteCell}
|
||||
cell={cell}
|
||||
handleClickOutside={this.closeMenu}
|
||||
/>
|
||||
}
|
||||
<div
|
||||
className={classnames('dash-graph--heading', {
|
||||
'dash-graph--heading-draggable': !shouldNotBeEditable,
|
||||
})}
|
||||
>
|
||||
{nameOrField}
|
||||
</div>
|
||||
{shouldNotBeEditable
|
||||
? null
|
||||
: <ContextMenu
|
||||
isOpen={this.state.isMenuOpen}
|
||||
toggleMenu={this.toggleMenu}
|
||||
onEdit={onSummonOverlayTechnologies}
|
||||
onRename={onStartRenaming}
|
||||
onDelete={onDeleteCell}
|
||||
cell={cell}
|
||||
handleClickOutside={this.closeMenu}
|
||||
/>}
|
||||
<div className="dash-graph--container">
|
||||
{children}
|
||||
</div>
|
||||
|
@ -123,16 +115,23 @@ const NameableGraph = React.createClass({
|
|||
},
|
||||
})
|
||||
|
||||
const ContextMenu = OnClickOutside(({isOpen, toggleMenu, onEdit, onRename, onDelete, cell}) => (
|
||||
<div className={classnames('dash-graph--options', {'dash-graph--options-show': isOpen})} onClick={toggleMenu}>
|
||||
<button className="btn btn-info btn-xs">
|
||||
<span className="icon caret-down"></span>
|
||||
</button>
|
||||
<ul className="dash-graph--options-menu">
|
||||
<li onClick={() => onEdit(cell)}>Edit</li>
|
||||
<li onClick={onRename(cell.x, cell.y, cell.isEditing)}>Rename</li>
|
||||
<li onClick={() => onDelete(cell)}>Delete</li>
|
||||
</ul>
|
||||
</div>
|
||||
))
|
||||
const ContextMenu = OnClickOutside(
|
||||
({isOpen, toggleMenu, onEdit, onRename, onDelete, cell}) => (
|
||||
<div
|
||||
className={classnames('dash-graph--options', {
|
||||
'dash-graph--options-show': isOpen,
|
||||
})}
|
||||
onClick={toggleMenu}
|
||||
>
|
||||
<button className="btn btn-info btn-xs">
|
||||
<span className="icon caret-down" />
|
||||
</button>
|
||||
<ul className="dash-graph--options-menu">
|
||||
<li onClick={() => onEdit(cell)}>Edit</li>
|
||||
<li onClick={onRename(cell.x, cell.y, cell.isEditing)}>Rename</li>
|
||||
<li onClick={() => onDelete(cell)}>Delete</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
export default NameableGraph
|
||||
|
|
|
@ -12,7 +12,9 @@ const NoKapacitorError = React.createClass({
|
|||
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>
|
||||
<p>
|
||||
The current source does not have an associated Kapacitor instance, please configure one.
|
||||
</p>
|
||||
<Link to={path}>Add Kapacitor</Link>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -44,8 +44,13 @@ class Notifications extends Component {
|
|||
const {dismissNotification} = this.props
|
||||
|
||||
return (
|
||||
<button className="close" data-dismiss="alert" aria-label="Close" onClick={() => dismissNotification(type)}>
|
||||
<span className="icon remove"></span>
|
||||
<button
|
||||
className="close"
|
||||
data-dismiss="alert"
|
||||
aria-label="Close"
|
||||
onClick={() => dismissNotification(type)}
|
||||
>
|
||||
<span className="icon remove" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -66,11 +71,7 @@ class Notifications extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {
|
||||
func,
|
||||
shape,
|
||||
string,
|
||||
} = PropTypes
|
||||
const {func, shape, string} = PropTypes
|
||||
|
||||
Notifications.propTypes = {
|
||||
location: shape({
|
||||
|
@ -90,10 +91,15 @@ const mapStateToProps = ({notifications}) => ({
|
|||
notifications,
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
publishNotification: bindActionCreators(publishNotificationAction, dispatch),
|
||||
dismissNotification: bindActionCreators(dismissNotificationAction, dispatch),
|
||||
dismissAllNotifications: bindActionCreators(dismissAllNotificationsAction, dispatch),
|
||||
dismissAllNotifications: bindActionCreators(
|
||||
dismissAllNotificationsAction,
|
||||
dispatch
|
||||
),
|
||||
})
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Notifications))
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(
|
||||
withRouter(Notifications)
|
||||
)
|
||||
|
|
|
@ -17,14 +17,21 @@ export default function enhanceWithClickOutside(WrappedComponent) {
|
|||
|
||||
handleClickOutside(e) {
|
||||
const domNode = ReactDOM.findDOMNode(this)
|
||||
if ((!domNode || !domNode.contains(e.target)) &&
|
||||
typeof this.wrappedComponent.handleClickOutside === 'function') {
|
||||
if (
|
||||
(!domNode || !domNode.contains(e.target)) &&
|
||||
typeof this.wrappedComponent.handleClickOutside === 'function'
|
||||
) {
|
||||
this.wrappedComponent.handleClickOutside(e)
|
||||
}
|
||||
},
|
||||
|
||||
render() {
|
||||
return <WrappedComponent {...this.props} ref={(ref) => this.wrappedComponent = ref} />
|
||||
return (
|
||||
<WrappedComponent
|
||||
{...this.props}
|
||||
ref={ref => (this.wrappedComponent = ref)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, {PropTypes} from 'react'
|
||||
|
||||
const OverlayTechnologies = ({children}) => <div className="overlay-technology">{children}</div>
|
||||
const OverlayTechnologies = ({children}) => (
|
||||
<div className="overlay-technology">{children}</div>
|
||||
)
|
||||
|
||||
const {
|
||||
node,
|
||||
} = PropTypes
|
||||
const {node} = PropTypes
|
||||
|
||||
OverlayTechnologies.propTypes = {
|
||||
children: node.isRequired,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue