prettier --single-quote --trailing-comma es5 --bracket-spacing false --semi false --write "src/**/*.js"

pull/10616/head
Hunter Trujillo 2017-04-28 16:37:19 -06:00
parent 9d096c2afe
commit 4cbcc77fff
139 changed files with 2479 additions and 1630 deletions

View File

@ -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),
})

View File

@ -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',

View File

@ -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),

View File

@ -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})
}
}

View File

@ -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({

View File

@ -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>

View File

@ -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,

View File

@ -3,17 +3,15 @@ import React, {PropTypes} from 'react'
const EmptyRow = ({tableName}) => (
<tr className="table-empty-state">
<th colSpan="5">
<p>You don&#39;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

View File

@ -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,

View File

@ -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()),

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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()),

View File

@ -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,

View File

@ -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),

View File

@ -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),
})

View File

@ -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),

View File

@ -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}

View File

@ -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>
)

View File

@ -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({

View File

@ -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,

View File

@ -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({

View File

@ -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}

View File

@ -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,

View File

@ -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({}),

View File

@ -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,

View File

@ -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>
)

View File

@ -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)

View File

@ -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>
)

View File

@ -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: []}
}),
})

View File

@ -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>
)
},

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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>
)

View File

@ -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>
)
},

View File

@ -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}
/>

View File

@ -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}

View File

@ -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>

View File

@ -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},

View File

@ -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 */

View File

@ -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>
)

View File

@ -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),
})

View File

@ -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} />

View File

@ -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

View File

@ -1,6 +1,6 @@
import {toString} from './ast'
const InfluxQL = (ast) => {
const InfluxQL = ast => {
return {
// select: () =>
toString: () => toString(ast),

View File

@ -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}`)
)
})
}
}

View File

@ -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">

View File

@ -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,

View File

@ -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() {

View File

@ -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(),

View File

@ -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(),

View File

@ -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)
}
},

View File

@ -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

View File

@ -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 &quot;value&quot; }}"
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>
},
})

View File

@ -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,

View File

@ -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>
)
},

View File

@ -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>

View File

@ -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>

View File

@ -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} />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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.

View File

@ -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>

View File

@ -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>&#91;key=value,&#93;+</code>. If no groupBy is performed equal to literal &quot;nil&quot;'},
tags: {label: '{{.Tags}}', text: 'Map of tags. Use <code>&#123;&#123; index .Tags &quot;key&quot; &#125;&#125;</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>&#123;&#123; index .Fields &quot;key&quot; &#125;&#125;</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>&#91;key=value,&#93;+</code>. If no groupBy is performed equal to literal &quot;nil&quot;',
},
tags: {
label: '{{.Tags}}',
text: 'Map of tags. Use <code>&#123;&#123; index .Tags &quot;key&quot; &#125;&#125;</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>&#123;&#123; index .Fields &quot;key&quot; &#125;&#125;</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('.'),
}

View File

@ -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,

View File

@ -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 (

View File

@ -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),
}

View File

@ -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

View File

@ -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>`

View File

@ -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,
}

View File

@ -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

View File

@ -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
}

View File

@ -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,

View File

@ -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,

View File

@ -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}

View File

@ -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},
})

View File

@ -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>

View File

@ -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,
}

View File

@ -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,

View File

@ -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>

View File

@ -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()

View File

@ -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

View File

@ -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)
},

View File

@ -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>
)
},

View File

@ -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,

View File

@ -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}
},
})

View File

@ -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,

View File

@ -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

View File

@ -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>
)

View File

@ -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)
)

View File

@ -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)}
/>
)
},
})
}

View File

@ -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