Merge pull request #1721 from influxdata/feature/tickscript-editor
TICKscript Editorpull/10616/head
commit
56dcc31d82
|
@ -9,6 +9,7 @@
|
||||||
### Features
|
### Features
|
||||||
1. [#1885](https://github.com/influxdata/chronograf/pull/1885): Add `fill` options to data explorer and dashboard queries
|
1. [#1885](https://github.com/influxdata/chronograf/pull/1885): Add `fill` options to data explorer and dashboard queries
|
||||||
1. [#1978](https://github.com/influxdata/chronograf/pull/1978): Support editing kapacitor TICKScript
|
1. [#1978](https://github.com/influxdata/chronograf/pull/1978): Support editing kapacitor TICKScript
|
||||||
|
1. [#1721](https://github.com/influxdata/chronograf/pull/1721): Introduce the TICKscript editor UI
|
||||||
1. [#1992](https://github.com/influxdata/chronograf/pull/1992): Add .csv download button to data explorer
|
1. [#1992](https://github.com/influxdata/chronograf/pull/1992): Add .csv download button to data explorer
|
||||||
|
|
||||||
### UI Improvements
|
### UI Improvements
|
||||||
|
@ -157,6 +158,8 @@
|
||||||
1. [#1738](https://github.com/influxdata/chronograf/pull/1738): Add shared secret JWT authorization to InfluxDB
|
1. [#1738](https://github.com/influxdata/chronograf/pull/1738): Add shared secret JWT authorization to InfluxDB
|
||||||
1. [#1724](https://github.com/influxdata/chronograf/pull/1724): Add Pushover alert support
|
1. [#1724](https://github.com/influxdata/chronograf/pull/1724): Add Pushover alert support
|
||||||
1. [#1762](https://github.com/influxdata/chronograf/pull/1762): Restore all supported Kapacitor services when creating rules, and add most optional message parameters
|
1. [#1762](https://github.com/influxdata/chronograf/pull/1762): Restore all supported Kapacitor services when creating rules, and add most optional message parameters
|
||||||
|
1. [#1681](https://github.com/influxdata/chronograf/pull/1681): Add the ability to select Custom Time Ranges in the Hostpages, Data Explorer, and Dashboards.
|
||||||
|
1. [#1717](https://github.com/influxdata/chronograf/pull/1717): View server generated TICKscripts
|
||||||
|
|
||||||
### UI Improvements
|
### UI Improvements
|
||||||
1. [#1707](https://github.com/influxdata/chronograf/pull/1707): Polish alerts table in status page to wrap text less
|
1. [#1707](https://github.com/influxdata/chronograf/pull/1707): Polish alerts table in status page to wrap text less
|
||||||
|
|
|
@ -39,6 +39,8 @@ type kapaLinks struct {
|
||||||
Proxy string `json:"proxy"` // URL location of proxy endpoint for this source
|
Proxy string `json:"proxy"` // URL location of proxy endpoint for this source
|
||||||
Self string `json:"self"` // Self link mapping to this resource
|
Self string `json:"self"` // Self link mapping to this resource
|
||||||
Rules string `json:"rules"` // Rules link for defining roles alerts for kapacitor
|
Rules string `json:"rules"` // Rules link for defining roles alerts for kapacitor
|
||||||
|
Tasks string `json:"tasks"` // Tasks link to define a task against the proxy
|
||||||
|
Ping string `json:"ping"` // Ping path to kapacitor
|
||||||
}
|
}
|
||||||
|
|
||||||
type kapacitor struct {
|
type kapacitor struct {
|
||||||
|
@ -108,6 +110,8 @@ func newKapacitor(srv chronograf.Server) kapacitor {
|
||||||
Self: fmt.Sprintf("%s/%d/kapacitors/%d", httpAPISrcs, srv.SrcID, srv.ID),
|
Self: fmt.Sprintf("%s/%d/kapacitors/%d", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
Proxy: fmt.Sprintf("%s/%d/kapacitors/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID),
|
Proxy: fmt.Sprintf("%s/%d/kapacitors/%d/proxy", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
Rules: fmt.Sprintf("%s/%d/kapacitors/%d/rules", httpAPISrcs, srv.SrcID, srv.ID),
|
Rules: fmt.Sprintf("%s/%d/kapacitors/%d/rules", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
|
Tasks: fmt.Sprintf("%s/%d/kapacitors/%d/proxy?path=/kapacitor/v1/tasks", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
|
Ping: fmt.Sprintf("%s/%d/kapacitors/%d/proxy?path=/kapacitor/v1/ping", httpAPISrcs, srv.SrcID, srv.ID),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
"webpack-dev-server": "^1.14.1"
|
"webpack-dev-server": "^1.14.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@skidding/react-codemirror": "^1.0.1",
|
||||||
"axios": "^0.13.1",
|
"axios": "^0.13.1",
|
||||||
"bignumber.js": "^4.0.2",
|
"bignumber.js": "^4.0.2",
|
||||||
"bootstrap": "^3.3.7",
|
"bootstrap": "^3.3.7",
|
||||||
|
@ -112,6 +113,7 @@
|
||||||
"query-string": "^5.0.0",
|
"query-string": "^5.0.0",
|
||||||
"react": "^15.0.2",
|
"react": "^15.0.2",
|
||||||
"react-addons-shallow-compare": "^15.0.2",
|
"react-addons-shallow-compare": "^15.0.2",
|
||||||
|
"react-codemirror": "^1.0.0",
|
||||||
"react-custom-scrollbars": "^4.1.1",
|
"react-custom-scrollbars": "^4.1.1",
|
||||||
"react-dimensions": "^1.2.0",
|
"react-dimensions": "^1.2.0",
|
||||||
"react-dom": "^15.0.2",
|
"react-dom": "^15.0.2",
|
||||||
|
|
|
@ -10,7 +10,7 @@ import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
|
||||||
import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
|
import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
|
||||||
|
|
||||||
const RoleRow = ({
|
const RoleRow = ({
|
||||||
role: {name, permissions, users},
|
role: {name: roleName, permissions, users = []},
|
||||||
role,
|
role,
|
||||||
allUsers,
|
allUsers,
|
||||||
allPermissions,
|
allPermissions,
|
||||||
|
@ -23,12 +23,14 @@ const RoleRow = ({
|
||||||
onUpdateRoleUsers,
|
onUpdateRoleUsers,
|
||||||
onUpdateRolePermissions,
|
onUpdateRolePermissions,
|
||||||
}) => {
|
}) => {
|
||||||
const handleUpdateUsers = u => {
|
const handleUpdateUsers = usrs => {
|
||||||
onUpdateRoleUsers(role, u.map(n => ({name: n})))
|
onUpdateRoleUsers(role, usrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdatePermissions = allowed => {
|
const handleUpdatePermissions = allowed => {
|
||||||
onUpdateRolePermissions(role, [{scope: 'all', allowed}])
|
onUpdateRolePermissions(role, [
|
||||||
|
{scope: 'all', allowed: allowed.map(({name}) => name)},
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
const perms = _.get(permissions, ['0', 'allowed'], [])
|
const perms = _.get(permissions, ['0', 'allowed'], [])
|
||||||
|
@ -62,13 +64,13 @@ const RoleRow = ({
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td style={{width: `${ROLES_TABLE.colName}px`}}>
|
<td style={{width: `${ROLES_TABLE.colName}px`}}>
|
||||||
{name}
|
{roleName}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{allPermissions && allPermissions.length
|
{allPermissions && allPermissions.length
|
||||||
? <MultiSelectDropdown
|
? <MultiSelectDropdown
|
||||||
items={allPermissions}
|
items={allPermissions.map(name => ({name}))}
|
||||||
selectedItems={perms}
|
selectedItems={perms.map(name => ({name}))}
|
||||||
label={perms.length ? '' : 'Select Permissions'}
|
label={perms.length ? '' : 'Select Permissions'}
|
||||||
onApply={handleUpdatePermissions}
|
onApply={handleUpdatePermissions}
|
||||||
buttonSize="btn-xs"
|
buttonSize="btn-xs"
|
||||||
|
@ -85,9 +87,9 @@ const RoleRow = ({
|
||||||
<td>
|
<td>
|
||||||
{allUsers && allUsers.length
|
{allUsers && allUsers.length
|
||||||
? <MultiSelectDropdown
|
? <MultiSelectDropdown
|
||||||
items={allUsers.map(u => u.name)}
|
items={allUsers}
|
||||||
selectedItems={users === undefined ? [] : users.map(u => u.name)}
|
selectedItems={users}
|
||||||
label={users && users.length ? '' : 'Select Users'}
|
label={users.length ? '' : 'Select Users'}
|
||||||
onApply={handleUpdateUsers}
|
onApply={handleUpdateUsers}
|
||||||
buttonSize="btn-xs"
|
buttonSize="btn-xs"
|
||||||
buttonColor="btn-primary"
|
buttonColor="btn-primary"
|
||||||
|
|
|
@ -12,7 +12,7 @@ import ChangePassRow from 'src/admin/components/ChangePassRow'
|
||||||
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
|
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
|
||||||
|
|
||||||
const UserRow = ({
|
const UserRow = ({
|
||||||
user: {name, roles, permissions, password},
|
user: {name, roles = [], permissions, password},
|
||||||
user,
|
user,
|
||||||
allRoles,
|
allRoles,
|
||||||
allPermissions,
|
allPermissions,
|
||||||
|
@ -27,14 +27,15 @@ const UserRow = ({
|
||||||
onUpdateRoles,
|
onUpdateRoles,
|
||||||
onUpdatePassword,
|
onUpdatePassword,
|
||||||
}) => {
|
}) => {
|
||||||
const handleUpdatePermissions = allowed => {
|
const handleUpdatePermissions = perms => {
|
||||||
|
const allowed = perms.map(p => p.name)
|
||||||
onUpdatePermissions(user, [{scope: 'all', allowed}])
|
onUpdatePermissions(user, [{scope: 'all', allowed}])
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleUpdateRoles = roleNames => {
|
const handleUpdateRoles = roleNames => {
|
||||||
onUpdateRoles(
|
onUpdateRoles(
|
||||||
user,
|
user,
|
||||||
allRoles.filter(r => roleNames.find(rn => rn === r.name))
|
allRoles.filter(r => roleNames.find(rn => rn.name === r.name))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +43,8 @@ const UserRow = ({
|
||||||
onUpdatePassword(user, password)
|
onUpdatePassword(user, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const perms = _.get(permissions, ['0', 'allowed'], [])
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
return (
|
return (
|
||||||
<tr className="admin-table--edit-row">
|
<tr className="admin-table--edit-row">
|
||||||
|
@ -85,13 +88,9 @@ const UserRow = ({
|
||||||
{hasRoles
|
{hasRoles
|
||||||
? <td>
|
? <td>
|
||||||
<MultiSelectDropdown
|
<MultiSelectDropdown
|
||||||
items={allRoles.map(r => r.name)}
|
items={allRoles}
|
||||||
selectedItems={
|
selectedItems={roles.map(r => ({name: r.name}))}
|
||||||
roles
|
label={roles.length ? '' : 'Select Roles'}
|
||||||
? roles.map(r => r.name)
|
|
||||||
: [] /* TODO remove check when server returns empty list */
|
|
||||||
}
|
|
||||||
label={roles && roles.length ? '' : 'Select Roles'}
|
|
||||||
onApply={handleUpdateRoles}
|
onApply={handleUpdateRoles}
|
||||||
buttonSize="btn-xs"
|
buttonSize="btn-xs"
|
||||||
buttonColor="btn-primary"
|
buttonColor="btn-primary"
|
||||||
|
@ -104,8 +103,8 @@ const UserRow = ({
|
||||||
<td>
|
<td>
|
||||||
{allPermissions && allPermissions.length
|
{allPermissions && allPermissions.length
|
||||||
? <MultiSelectDropdown
|
? <MultiSelectDropdown
|
||||||
items={allPermissions}
|
items={allPermissions.map(p => ({name: p}))}
|
||||||
selectedItems={_.get(permissions, ['0', 'allowed'], [])}
|
selectedItems={perms.map(p => ({name: p}))}
|
||||||
label={
|
label={
|
||||||
permissions && permissions.length ? '' : 'Select Permissions'
|
permissions && permissions.length ? '' : 'Select Permissions'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,349 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
const CodeMirror = require('codemirror')
|
||||||
|
|
||||||
|
CodeMirror.defineSimpleMode = function(name, states) {
|
||||||
|
CodeMirror.defineMode(name, function(config) {
|
||||||
|
return CodeMirror.simpleMode(config, states)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.simpleMode = function(config, states) {
|
||||||
|
ensureState(states, 'start')
|
||||||
|
const states_ = {},
|
||||||
|
meta = states.meta || {}
|
||||||
|
let hasIndentation = false
|
||||||
|
for (const state in states) {
|
||||||
|
if (state !== meta && states.hasOwnProperty(state)) {
|
||||||
|
const list = (states_[state] = []),
|
||||||
|
orig = states[state]
|
||||||
|
for (let i = 0; i < orig.length; i++) {
|
||||||
|
const data = orig[i]
|
||||||
|
list.push(new Rule(data, states))
|
||||||
|
if (data.indent || data.dedent) {
|
||||||
|
hasIndentation = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mode = {
|
||||||
|
startState() {
|
||||||
|
return {
|
||||||
|
state: 'start',
|
||||||
|
pending: null,
|
||||||
|
local: null,
|
||||||
|
localState: null,
|
||||||
|
indent: hasIndentation ? [] : null,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
copyState(state) {
|
||||||
|
const s = {
|
||||||
|
state: state.state,
|
||||||
|
pending: state.pending,
|
||||||
|
local: state.local,
|
||||||
|
localState: null,
|
||||||
|
indent: state.indent && state.indent.slice(0),
|
||||||
|
}
|
||||||
|
if (state.localState) {
|
||||||
|
s.localState = CodeMirror.copyState(state.local.mode, state.localState)
|
||||||
|
}
|
||||||
|
if (state.stack) {
|
||||||
|
s.stack = state.stack.slice(0)
|
||||||
|
}
|
||||||
|
for (let pers = state.persistentStates; pers; pers = pers.next) {
|
||||||
|
s.persistentStates = {
|
||||||
|
mode: pers.mode,
|
||||||
|
spec: pers.spec,
|
||||||
|
state:
|
||||||
|
pers.state === state.localState
|
||||||
|
? s.localState
|
||||||
|
: CodeMirror.copyState(pers.mode, pers.state),
|
||||||
|
next: s.persistentStates,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
},
|
||||||
|
token: tokenFunction(states_, config),
|
||||||
|
innerMode(state) {
|
||||||
|
return state.local && {mode: state.local.mode, state: state.localState}
|
||||||
|
},
|
||||||
|
indent: indentFunction(states_, meta),
|
||||||
|
}
|
||||||
|
if (meta) {
|
||||||
|
for (const prop in meta) {
|
||||||
|
if (meta.hasOwnProperty(prop)) {
|
||||||
|
mode[prop] = meta[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mode
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureState(states, name) {
|
||||||
|
if (!states.hasOwnProperty(name)) {
|
||||||
|
throw new Error(`Undefined state ${name} in simple mode`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toRegex(val, caret) {
|
||||||
|
if (!val) {
|
||||||
|
return /(?:)/
|
||||||
|
}
|
||||||
|
let flags = ''
|
||||||
|
if (val instanceof RegExp) {
|
||||||
|
if (val.ignoreCase) {
|
||||||
|
flags = 'i'
|
||||||
|
}
|
||||||
|
val = val.source
|
||||||
|
} else {
|
||||||
|
val = String(val)
|
||||||
|
}
|
||||||
|
return new RegExp(`${caret === false ? '' : '^'}(?:${val})`, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
function asToken(val) {
|
||||||
|
if (!val) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (val.apply) {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
return val.replace(/\./g, ' ')
|
||||||
|
}
|
||||||
|
const result = []
|
||||||
|
for (let i = 0; i < val.length; i++) {
|
||||||
|
result.push(val[i] && val[i].replace(/\./g, ' '))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function Rule(data, states) {
|
||||||
|
if (data.next || data.push) {
|
||||||
|
ensureState(states, data.next || data.push)
|
||||||
|
}
|
||||||
|
this.regex = toRegex(data.regex)
|
||||||
|
this.token = asToken(data.token)
|
||||||
|
this.data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
function tokenFunction(states, config) {
|
||||||
|
return function(stream, state) {
|
||||||
|
if (state.pending) {
|
||||||
|
const pend = state.pending.shift()
|
||||||
|
if (state.pending.length === 0) {
|
||||||
|
state.pending = null
|
||||||
|
}
|
||||||
|
stream.pos += pend.text.length
|
||||||
|
return pend.token
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.local) {
|
||||||
|
let tok, m
|
||||||
|
if (state.local.end && stream.match(state.local.end)) {
|
||||||
|
tok = state.local.endToken || null
|
||||||
|
state.local = state.localState = null
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
tok = state.local.mode.token(stream, state.localState)
|
||||||
|
if (
|
||||||
|
state.local.endScan &&
|
||||||
|
(m = state.local.endScan.exec(stream.current()))
|
||||||
|
) {
|
||||||
|
stream.pos = stream.start + m.index
|
||||||
|
}
|
||||||
|
return tok
|
||||||
|
}
|
||||||
|
|
||||||
|
const curState = states[state.state]
|
||||||
|
for (let i = 0; i < curState.length; i++) {
|
||||||
|
const rule = curState[i]
|
||||||
|
const matches =
|
||||||
|
(!rule.data.sol || stream.sol()) && stream.match(rule.regex)
|
||||||
|
if (matches) {
|
||||||
|
if (rule.data.next) {
|
||||||
|
state.state = rule.data.next
|
||||||
|
} else if (rule.data.push) {
|
||||||
|
;(state.stack || (state.stack = [])).push(state.state)
|
||||||
|
state.state = rule.data.push
|
||||||
|
} else if (rule.data.pop && state.stack && state.stack.length) {
|
||||||
|
state.state = state.stack.pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rule.data.mode) {
|
||||||
|
enterLocalMode(config, state, rule.data.mode, rule.token)
|
||||||
|
}
|
||||||
|
if (rule.data.indent) {
|
||||||
|
state.indent.push(stream.indentation() + config.indentUnit)
|
||||||
|
}
|
||||||
|
if (rule.data.dedent) {
|
||||||
|
state.indent.pop()
|
||||||
|
}
|
||||||
|
let token = rule.token
|
||||||
|
if (token && token.apply) {
|
||||||
|
token = token(matches)
|
||||||
|
}
|
||||||
|
if (matches.length > 2) {
|
||||||
|
state.pending = []
|
||||||
|
for (let j = 2; j < matches.length; j++) {
|
||||||
|
if (matches[j]) {
|
||||||
|
state.pending.push({text: matches[j], token: rule.token[j - 1]})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.backUp(
|
||||||
|
matches[0].length - (matches[1] ? matches[1].length : 0)
|
||||||
|
)
|
||||||
|
return token[0]
|
||||||
|
} else if (token && token.join) {
|
||||||
|
return token[0]
|
||||||
|
}
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.next()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmp(a, b) {
|
||||||
|
if (a === b) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (!a || typeof a !== 'object' || !b || typeof b !== 'object') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
let props = 0
|
||||||
|
for (const prop in a) {
|
||||||
|
if (a.hasOwnProperty(prop)) {
|
||||||
|
if (!b.hasOwnProperty(prop) || !cmp(a[prop], b[prop])) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
props += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const prop in b) {
|
||||||
|
if (b.hasOwnProperty(prop)) {
|
||||||
|
props -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props === 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterLocalMode(config, state, spec, token) {
|
||||||
|
let pers
|
||||||
|
if (spec.persistent) {
|
||||||
|
for (let p = state.persistentStates; p && !pers; p = p.next) {
|
||||||
|
if (spec.spec ? cmp(spec.spec, p.spec) : spec.mode === p.mode) {
|
||||||
|
pers = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mode = pers
|
||||||
|
? pers.mode
|
||||||
|
: spec.mode || CodeMirror.getMode(config, spec.spec)
|
||||||
|
const lState = pers ? pers.state : CodeMirror.startState(mode)
|
||||||
|
if (spec.persistent && !pers) {
|
||||||
|
state.persistentStates = {
|
||||||
|
mode,
|
||||||
|
spec: spec.spec,
|
||||||
|
state: lState,
|
||||||
|
next: state.persistentStates,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.localState = lState
|
||||||
|
state.local = {
|
||||||
|
mode,
|
||||||
|
end: spec.end && toRegex(spec.end),
|
||||||
|
endScan: spec.end && spec.forceEnd !== false && toRegex(spec.end, false),
|
||||||
|
endToken: token && token.join ? token[token.length - 1] : token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexOf(val, arr) {
|
||||||
|
for (let i = 0; i < arr.length; i++) {
|
||||||
|
if (arr[i] === val) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function indentFunction(states, meta) {
|
||||||
|
return function(state, textAfter, line) {
|
||||||
|
if (state.local && state.local.mode.indent) {
|
||||||
|
return state.local.mode.indent(state.localState, textAfter, line)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
state.indent === null ||
|
||||||
|
state.local ||
|
||||||
|
(meta.dontIndentStates &&
|
||||||
|
indexOf(state.state, meta.dontIndentStates) > -1)
|
||||||
|
) {
|
||||||
|
return CodeMirror.Pass
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = state.indent.length - 1,
|
||||||
|
rules = states[state.state]
|
||||||
|
scan: for (;;) {
|
||||||
|
for (let i = 0; i < rules.length; i++) {
|
||||||
|
const rule = rules[i]
|
||||||
|
if (rule.data.dedent && rule.data.dedentIfLineStart !== false) {
|
||||||
|
const m = rule.regex.exec(textAfter)
|
||||||
|
if (m && m[0]) {
|
||||||
|
pos -= 1
|
||||||
|
if (rule.next || rule.push) {
|
||||||
|
rules = states[rule.next || rule.push]
|
||||||
|
}
|
||||||
|
textAfter = textAfter.slice(m[0].length)
|
||||||
|
continue scan
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return pos < 0 ? 0 : state.indent[pos]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CodeMirror.defineSimpleMode('tickscript', {
|
||||||
|
// The start state contains the rules that are intially used
|
||||||
|
start: [
|
||||||
|
// The regex matches the token, the token property contains the type
|
||||||
|
{regex: /"(?:[^\\]|\\.)*?(?:"|$)/, token: 'string.double'},
|
||||||
|
{regex: /'(?:[^\\]|\\.)*?(?:'|$)/, token: 'string.single'},
|
||||||
|
{
|
||||||
|
regex: /(function)(\s+)([a-z$][\w$]*)/,
|
||||||
|
token: ['keyword', null, 'variable-2'],
|
||||||
|
},
|
||||||
|
// Rules are matched in the order in which they appear, so there is
|
||||||
|
// no ambiguity between this one and the one above
|
||||||
|
{
|
||||||
|
regex: /(?:var|return|if|for|while|else|do|this|stream|batch|influxql|lambda)/,
|
||||||
|
token: 'keyword',
|
||||||
|
},
|
||||||
|
{regex: /true|false|null|undefined|TRUE|FALSE/, token: 'atom'},
|
||||||
|
{
|
||||||
|
regex: /0x[a-f\d]+|[-+]?(?:\.\d+|\d+\.?\d*)(?:e[-+]?\d+)?/i,
|
||||||
|
token: 'number',
|
||||||
|
},
|
||||||
|
{regex: /\/\/.*/, token: 'comment'},
|
||||||
|
{regex: /\/(?:[^\\]|\\.)*?\//, token: 'variable-3'},
|
||||||
|
// A next property will cause the mode to move to a different state
|
||||||
|
{regex: /\/\*/, token: 'comment', next: 'comment'},
|
||||||
|
{regex: /[-+\/*=<>!]+/, token: 'operator'},
|
||||||
|
{regex: /[a-z$][\w$]*/, token: 'variable'},
|
||||||
|
],
|
||||||
|
// The multi-line comment state.
|
||||||
|
comment: [
|
||||||
|
{regex: /.*?\*\//, token: 'comment', next: 'start'},
|
||||||
|
{regex: /.*/, token: 'comment'},
|
||||||
|
],
|
||||||
|
// The meta property contains global information about the mode. It
|
||||||
|
// can contain properties like lineComment, which are supported by
|
||||||
|
// all modes, and also directives like dontIndentStates, which are
|
||||||
|
// specific to simple modes.
|
||||||
|
meta: {
|
||||||
|
dontIndentStates: ['comment'],
|
||||||
|
lineComment: '//',
|
||||||
|
},
|
||||||
|
})
|
|
@ -23,6 +23,7 @@ import {
|
||||||
KapacitorRulePage,
|
KapacitorRulePage,
|
||||||
KapacitorRulesPage,
|
KapacitorRulesPage,
|
||||||
KapacitorTasksPage,
|
KapacitorTasksPage,
|
||||||
|
TickscriptPage,
|
||||||
} from 'src/kapacitor'
|
} from 'src/kapacitor'
|
||||||
import {AdminPage} from 'src/admin'
|
import {AdminPage} from 'src/admin'
|
||||||
import {CreateSource, SourcePage, ManageSources} from 'src/sources'
|
import {CreateSource, SourcePage, ManageSources} from 'src/sources'
|
||||||
|
@ -140,6 +141,8 @@ const Root = React.createClass({
|
||||||
<Route path="alert-rules" component={KapacitorRulesPage} />
|
<Route path="alert-rules" component={KapacitorRulesPage} />
|
||||||
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
<Route path="alert-rules/:ruleID" component={KapacitorRulePage} />
|
||||||
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
<Route path="alert-rules/new" component={KapacitorRulePage} />
|
||||||
|
<Route path="tickscript/new" component={TickscriptPage} />
|
||||||
|
<Route path="tickscript/:ruleID" component={TickscriptPage} />
|
||||||
<Route path="kapacitors/new" component={KapacitorPage} />
|
<Route path="kapacitors/new" component={KapacitorPage} />
|
||||||
<Route path="kapacitors/:id/edit" component={KapacitorPage} />
|
<Route path="kapacitors/:id/edit" component={KapacitorPage} />
|
||||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||||
|
|
|
@ -3,9 +3,11 @@ import {getActiveKapacitor} from 'shared/apis'
|
||||||
import {publishNotification} from 'shared/actions/notifications'
|
import {publishNotification} from 'shared/actions/notifications'
|
||||||
import {
|
import {
|
||||||
getRules,
|
getRules,
|
||||||
getRule,
|
getRule as getRuleAJAX,
|
||||||
deleteRule as deleteRuleAPI,
|
deleteRule as deleteRuleAPI,
|
||||||
updateRuleStatus as updateRuleStatusAPI,
|
updateRuleStatus as updateRuleStatusAPI,
|
||||||
|
createTask as createTaskAJAX,
|
||||||
|
updateTask as updateTaskAJAX,
|
||||||
} from 'src/kapacitor/apis'
|
} from 'src/kapacitor/apis'
|
||||||
import {errorThrown} from 'shared/actions/errors'
|
import {errorThrown} from 'shared/actions/errors'
|
||||||
|
|
||||||
|
@ -19,7 +21,7 @@ const loadQuery = query => ({
|
||||||
export function fetchRule(source, ruleID) {
|
export function fetchRule(source, ruleID) {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
getActiveKapacitor(source).then(kapacitor => {
|
getActiveKapacitor(source).then(kapacitor => {
|
||||||
getRule(kapacitor, ruleID).then(({data: rule}) => {
|
getRuleAJAX(kapacitor, ruleID).then(({data: rule}) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: 'LOAD_RULE',
|
type: 'LOAD_RULE',
|
||||||
payload: {
|
payload: {
|
||||||
|
@ -39,6 +41,31 @@ const addQuery = queryID => ({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const getRule = (kapacitor, ruleID) => async dispatch => {
|
||||||
|
try {
|
||||||
|
const {data: rule} = await getRuleAJAX(kapacitor, ruleID)
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'LOAD_RULE',
|
||||||
|
payload: {
|
||||||
|
rule: {...rule, queryID: rule.query && rule.query.id},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if (rule.query) {
|
||||||
|
dispatch({
|
||||||
|
type: 'LOAD_KAPACITOR_QUERY',
|
||||||
|
payload: {
|
||||||
|
query: rule.query,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function loadDefaultRule() {
|
export function loadDefaultRule() {
|
||||||
return dispatch => {
|
return dispatch => {
|
||||||
const queryID = uuid.v4()
|
const queryID = uuid.v4()
|
||||||
|
@ -208,3 +235,46 @@ export function updateRuleStatus(rule, status) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createTask = (
|
||||||
|
kapacitor,
|
||||||
|
task,
|
||||||
|
router,
|
||||||
|
sourceID
|
||||||
|
) => async dispatch => {
|
||||||
|
try {
|
||||||
|
const {data} = await createTaskAJAX(kapacitor, task)
|
||||||
|
router.push(`/sources/${sourceID}/alert-rules`)
|
||||||
|
dispatch(publishNotification('success', 'You made a TICKscript!'))
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
if (!error) {
|
||||||
|
dispatch(errorThrown('Could not communicate with server'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTask = (
|
||||||
|
kapacitor,
|
||||||
|
task,
|
||||||
|
ruleID,
|
||||||
|
router,
|
||||||
|
sourceID
|
||||||
|
) => async dispatch => {
|
||||||
|
try {
|
||||||
|
const {data} = await updateTaskAJAX(kapacitor, task, ruleID, sourceID)
|
||||||
|
router.push(`/sources/${sourceID}/alert-rules`)
|
||||||
|
dispatch(publishNotification('success', 'TICKscript updated successully'))
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
if (!error) {
|
||||||
|
dispatch(errorThrown('Could not communicate with server'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import AJAX from 'utils/ajax'
|
import AJAX from 'utils/ajax'
|
||||||
|
|
||||||
function rangeRule(rule) {
|
const rangeRule = rule => {
|
||||||
const {value, rangeValue, operator} = rule.values
|
const {value, rangeValue, operator} = rule.values
|
||||||
|
|
||||||
if (operator === 'inside range' || operator === 'outside range') {
|
if (operator === 'inside range' || operator === 'outside range') {
|
||||||
|
@ -11,7 +11,7 @@ function rangeRule(rule) {
|
||||||
return rule
|
return rule
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createRule(kapacitor, rule) {
|
export const createRule = (kapacitor, rule) => {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: kapacitor.links.rules,
|
url: kapacitor.links.rules,
|
||||||
|
@ -19,21 +19,26 @@ export function createRule(kapacitor, rule) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRules(kapacitor) {
|
export const getRules = kapacitor => {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: kapacitor.links.rules,
|
url: kapacitor.links.rules,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRule(kapacitor, ruleID) {
|
export const getRule = async (kapacitor, ruleID) => {
|
||||||
return AJAX({
|
try {
|
||||||
method: 'GET',
|
return await AJAX({
|
||||||
url: `${kapacitor.links.rules}/${ruleID}`,
|
method: 'GET',
|
||||||
})
|
url: `${kapacitor.links.rules}/${ruleID}`,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function editRule(rule) {
|
export const editRule = rule => {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
url: rule.links.self,
|
url: rule.links.self,
|
||||||
|
@ -41,17 +46,57 @@ export function editRule(rule) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteRule(rule) {
|
export const deleteRule = rule => {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: rule.links.self,
|
url: rule.links.self,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateRuleStatus(rule, status) {
|
export const updateRuleStatus = (rule, status) => {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: rule.links.self,
|
url: rule.links.self,
|
||||||
data: {status},
|
data: {status},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createTask = async (kapacitor, {id, dbrps, tickscript, type}) => {
|
||||||
|
try {
|
||||||
|
return await AJAX({
|
||||||
|
method: 'POST',
|
||||||
|
url: kapacitor.links.rules,
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
dbrps,
|
||||||
|
tickscript,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateTask = async (
|
||||||
|
kapacitor,
|
||||||
|
{id, dbrps, tickscript, type},
|
||||||
|
ruleID
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
return await AJAX({
|
||||||
|
method: 'PUT',
|
||||||
|
url: `${kapacitor.links.rules}/${ruleID}`,
|
||||||
|
data: {
|
||||||
|
id,
|
||||||
|
type,
|
||||||
|
dbrps,
|
||||||
|
tickscript,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import {Link} from 'react-router'
|
||||||
import NoKapacitorError from 'shared/components/NoKapacitorError'
|
import NoKapacitorError from 'shared/components/NoKapacitorError'
|
||||||
import SourceIndicator from 'shared/components/SourceIndicator'
|
import SourceIndicator from 'shared/components/SourceIndicator'
|
||||||
import KapacitorRulesTable from 'src/kapacitor/components/KapacitorRulesTable'
|
import KapacitorRulesTable from 'src/kapacitor/components/KapacitorRulesTable'
|
||||||
|
import TasksTable from 'src/kapacitor/components/TasksTable'
|
||||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||||
import TICKscriptOverlay from 'src/kapacitor/components/TICKscriptOverlay'
|
|
||||||
|
|
||||||
const KapacitorRules = ({
|
const KapacitorRules = ({
|
||||||
source,
|
source,
|
||||||
|
@ -13,10 +13,7 @@ const KapacitorRules = ({
|
||||||
hasKapacitor,
|
hasKapacitor,
|
||||||
loading,
|
loading,
|
||||||
onDelete,
|
onDelete,
|
||||||
tickscript,
|
|
||||||
onChangeRuleStatus,
|
onChangeRuleStatus,
|
||||||
onReadTickscript,
|
|
||||||
onCloseTickscript,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
|
@ -44,43 +41,72 @@ const KapacitorRules = ({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableHeader =
|
const rulez = rules.filter(r => r.query)
|
||||||
rules.length === 1 ? '1 Alert Rule' : `${rules.length} Alert Rules`
|
const tasks = rules.filter(r => !r.query)
|
||||||
|
|
||||||
|
const rHeader = `${rulez.length} Alert Rule${rulez.length === 1 ? '' : 's'}`
|
||||||
|
const tHeader = `${tasks.length} TICKscript${tasks.length === 1 ? '' : 's'}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContents
|
<PageContents source={source}>
|
||||||
source={source}
|
|
||||||
tickscript={tickscript}
|
|
||||||
onReadTickscript={onReadTickscript}
|
|
||||||
onCloseTickscript={onCloseTickscript}
|
|
||||||
>
|
|
||||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||||
<h2 className="panel-title">
|
<h2 className="panel-title">
|
||||||
{tableHeader}
|
{rHeader}
|
||||||
</h2>
|
</h2>
|
||||||
<Link
|
<div className="u-flex u-ai-center u-jc-space-between">
|
||||||
to={`/sources/${source.id}/alert-rules/new`}
|
<Link
|
||||||
className="btn btn-sm btn-primary"
|
to={`/sources/${source.id}/alert-rules/new`}
|
||||||
>
|
className="btn btn-sm btn-primary"
|
||||||
Create Rule
|
style={{marginRight: '4px'}}
|
||||||
</Link>
|
>
|
||||||
|
Build Rule
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<KapacitorRulesTable
|
<KapacitorRulesTable
|
||||||
source={source}
|
source={source}
|
||||||
rules={rules}
|
rules={rulez}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onReadTickscript={onReadTickscript}
|
|
||||||
onChangeRuleStatus={onChangeRuleStatus}
|
onChangeRuleStatus={onChangeRuleStatus}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-md-12">
|
||||||
|
<div className="panel panel-minimal">
|
||||||
|
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||||
|
<h2 className="panel-title">
|
||||||
|
{tHeader}
|
||||||
|
</h2>
|
||||||
|
<div className="u-flex u-ai-center u-jc-space-between">
|
||||||
|
<Link
|
||||||
|
to={`/sources/${source.id}/tickscript/new`}
|
||||||
|
className="btn btn-sm btn-info"
|
||||||
|
>
|
||||||
|
Write TICKscript
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TasksTable
|
||||||
|
source={source}
|
||||||
|
tasks={tasks}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onChangeRuleStatus={onChangeRuleStatus}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</PageContents>
|
</PageContents>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const PageContents = ({children, source, tickscript, onCloseTickscript}) =>
|
const PageContents = ({children, source}) =>
|
||||||
<div className="page">
|
<div className="page">
|
||||||
<div className="page-header">
|
<div className="page-header">
|
||||||
<div className="page-header__container">
|
<div className="page-header__container">
|
||||||
<div className="page-header__left">
|
<div className="page-header__left">
|
||||||
<h1 className="page-header__title">Alert Rules</h1>
|
<h1 className="page-header__title">
|
||||||
|
Build Alert Rules or Write TICKscripts
|
||||||
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className="page-header__right">
|
<div className="page-header__right">
|
||||||
<SourceIndicator sourceName={source && source.name} />
|
<SourceIndicator sourceName={source && source.name} />
|
||||||
|
@ -98,15 +124,9 @@ const PageContents = ({children, source, tickscript, onCloseTickscript}) =>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</FancyScrollbar>
|
</FancyScrollbar>
|
||||||
{tickscript
|
|
||||||
? <TICKscriptOverlay
|
|
||||||
tickscript={tickscript}
|
|
||||||
onClose={onCloseTickscript}
|
|
||||||
/>
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
const {arrayOf, bool, func, node, shape, string} = PropTypes
|
const {arrayOf, bool, func, node, shape} = PropTypes
|
||||||
|
|
||||||
KapacitorRules.propTypes = {
|
KapacitorRules.propTypes = {
|
||||||
source: shape(),
|
source: shape(),
|
||||||
|
@ -115,15 +135,11 @@ KapacitorRules.propTypes = {
|
||||||
loading: bool,
|
loading: bool,
|
||||||
onChangeRuleStatus: func,
|
onChangeRuleStatus: func,
|
||||||
onDelete: func,
|
onDelete: func,
|
||||||
tickscript: string,
|
|
||||||
onReadTickscript: func,
|
|
||||||
onCloseTickscript: func,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PageContents.propTypes = {
|
PageContents.propTypes = {
|
||||||
children: node,
|
children: node,
|
||||||
source: shape(),
|
source: shape(),
|
||||||
tickscript: string,
|
|
||||||
onCloseTickscript: func,
|
onCloseTickscript: func,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,26 +5,20 @@ import _ from 'lodash'
|
||||||
import {KAPACITOR_RULES_TABLE} from 'src/kapacitor/constants/tableSizing'
|
import {KAPACITOR_RULES_TABLE} from 'src/kapacitor/constants/tableSizing'
|
||||||
const {
|
const {
|
||||||
colName,
|
colName,
|
||||||
colType,
|
colTrigger,
|
||||||
colMessage,
|
colMessage,
|
||||||
colAlerts,
|
colAlerts,
|
||||||
colEnabled,
|
colEnabled,
|
||||||
colActions,
|
colActions,
|
||||||
} = KAPACITOR_RULES_TABLE
|
} = KAPACITOR_RULES_TABLE
|
||||||
|
|
||||||
const KapacitorRulesTable = ({
|
const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) =>
|
||||||
rules,
|
|
||||||
source,
|
|
||||||
onDelete,
|
|
||||||
onReadTickscript,
|
|
||||||
onChangeRuleStatus,
|
|
||||||
}) =>
|
|
||||||
<div className="panel-body">
|
<div className="panel-body">
|
||||||
<table className="table v-center">
|
<table className="table v-center">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style={{width: colName}}>Name</th>
|
<th style={{width: colName}}>Name</th>
|
||||||
<th style={{width: colType}}>Rule Type</th>
|
<th style={{width: colTrigger}}>Rule Trigger</th>
|
||||||
<th style={{width: colMessage}}>Message</th>
|
<th style={{width: colMessage}}>Message</th>
|
||||||
<th style={{width: colAlerts}}>Alerts</th>
|
<th style={{width: colAlerts}}>Alerts</th>
|
||||||
<th style={{width: colEnabled}} className="text-center">
|
<th style={{width: colEnabled}} className="text-center">
|
||||||
|
@ -42,7 +36,6 @@ const KapacitorRulesTable = ({
|
||||||
source={source}
|
source={source}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
onChangeRuleStatus={onChangeRuleStatus}
|
onChangeRuleStatus={onChangeRuleStatus}
|
||||||
onRead={onReadTickscript}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
@ -50,12 +43,14 @@ const KapacitorRulesTable = ({
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
const RuleRow = ({rule, source, onRead, onDelete, onChangeRuleStatus}) =>
|
const handleDelete = (rule, onDelete) => onDelete(rule)
|
||||||
|
|
||||||
|
const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) =>
|
||||||
<tr key={rule.id}>
|
<tr key={rule.id}>
|
||||||
<td style={{width: colName}} className="monotype">
|
<td style={{width: colName}} className="monotype">
|
||||||
<RuleTitle rule={rule} source={source} />
|
<RuleTitle rule={rule} source={source} />
|
||||||
</td>
|
</td>
|
||||||
<td style={{width: colType}} className="monotype">
|
<td style={{width: colTrigger}} className="monotype">
|
||||||
{rule.trigger}
|
{rule.trigger}
|
||||||
</td>
|
</td>
|
||||||
<td className="monotype">
|
<td className="monotype">
|
||||||
|
@ -82,10 +77,16 @@ const RuleRow = ({rule, source, onRead, onDelete, onChangeRuleStatus}) =>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
||||||
<button className="btn btn-info btn-xs" onClick={onRead(rule)}>
|
<Link
|
||||||
View TICKscript
|
className="btn btn-info btn-xs"
|
||||||
</button>
|
to={`/sources/${source.id}/tickscript/${rule.id}`}
|
||||||
<button className="btn btn-danger btn-xs" onClick={onDelete(rule)}>
|
>
|
||||||
|
Edit TICKscript
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger btn-xs"
|
||||||
|
onClick={handleDelete(rule, onDelete)}
|
||||||
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
@ -117,7 +118,6 @@ KapacitorRulesTable.propTypes = {
|
||||||
source: shape({
|
source: shape({
|
||||||
id: string.isRequired,
|
id: string.isRequired,
|
||||||
}).isRequired,
|
}).isRequired,
|
||||||
onReadTickscript: func,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RuleRow.propTypes = {
|
RuleRow.propTypes = {
|
||||||
|
@ -125,7 +125,6 @@ RuleRow.propTypes = {
|
||||||
source: shape(),
|
source: shape(),
|
||||||
onChangeRuleStatus: func,
|
onChangeRuleStatus: func,
|
||||||
onDelete: func,
|
onDelete: func,
|
||||||
onRead: func,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RuleTitle.propTypes = {
|
RuleTitle.propTypes = {
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
import {Link} from 'react-router'
|
||||||
|
import _ from 'lodash'
|
||||||
|
|
||||||
|
import {TASKS_TABLE} from 'src/kapacitor/constants/tableSizing'
|
||||||
|
|
||||||
|
const {colID, colType, colEnabled, colActions} = TASKS_TABLE
|
||||||
|
|
||||||
|
const TasksTable = ({tasks, source, onDelete, onChangeRuleStatus}) =>
|
||||||
|
<div className="panel-body">
|
||||||
|
<table className="table v-center">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style={{width: colID}}>ID</th>
|
||||||
|
<th style={{width: colType}}>Type</th>
|
||||||
|
<th style={{width: colEnabled}} className="text-center">
|
||||||
|
Enabled
|
||||||
|
</th>
|
||||||
|
<th style={{width: colActions}} />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{_.sortBy(tasks, t => t.name.toLowerCase()).map(task => {
|
||||||
|
return (
|
||||||
|
<TaskRow
|
||||||
|
key={task.id}
|
||||||
|
task={task}
|
||||||
|
source={source}
|
||||||
|
onDelete={onDelete}
|
||||||
|
onChangeRuleStatus={onChangeRuleStatus}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const handleDelete = (task, onDelete) => onDelete(task)
|
||||||
|
|
||||||
|
const TaskRow = ({task, source, onDelete, onChangeRuleStatus}) =>
|
||||||
|
<tr key={task.id}>
|
||||||
|
<td style={{width: colID}} className="monotype">
|
||||||
|
<i>
|
||||||
|
{task.id}
|
||||||
|
</i>
|
||||||
|
</td>
|
||||||
|
<td style={{width: colType}} className="monotype">
|
||||||
|
{task.type}
|
||||||
|
</td>
|
||||||
|
<td style={{width: colEnabled}} className="monotype text-center">
|
||||||
|
<div className="dark-checkbox">
|
||||||
|
<input
|
||||||
|
id={`kapacitor-enabled ${task.id}`}
|
||||||
|
className="form-control-static"
|
||||||
|
type="checkbox"
|
||||||
|
defaultChecked={task.status === 'enabled'}
|
||||||
|
onClick={onChangeRuleStatus(task)}
|
||||||
|
/>
|
||||||
|
<label htmlFor={`kapacitor-enabled ${task.id}`} />
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
||||||
|
<Link
|
||||||
|
className="btn btn-info btn-xs"
|
||||||
|
to={`/sources/${source.id}/tickscript/${task.id}`}
|
||||||
|
>
|
||||||
|
Edit TICKscript
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger btn-xs"
|
||||||
|
onClick={handleDelete(task, onDelete)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
const {arrayOf, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
TasksTable.propTypes = {
|
||||||
|
tasks: arrayOf(shape()),
|
||||||
|
onChangeRuleStatus: func,
|
||||||
|
onDelete: func,
|
||||||
|
source: shape({
|
||||||
|
id: string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
TaskRow.propTypes = {
|
||||||
|
task: shape(),
|
||||||
|
source: shape(),
|
||||||
|
onChangeRuleStatus: func,
|
||||||
|
onDelete: func,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TasksTable
|
|
@ -0,0 +1,65 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
import TickscriptHeader from 'src/kapacitor/components/TickscriptHeader'
|
||||||
|
import TickscriptEditor from 'src/kapacitor/components/TickscriptEditor'
|
||||||
|
|
||||||
|
const Tickscript = ({
|
||||||
|
source,
|
||||||
|
onSave,
|
||||||
|
task,
|
||||||
|
validation,
|
||||||
|
onSelectDbrps,
|
||||||
|
onChangeScript,
|
||||||
|
onChangeType,
|
||||||
|
onChangeID,
|
||||||
|
isNewTickscript,
|
||||||
|
}) =>
|
||||||
|
<div className="page">
|
||||||
|
<TickscriptHeader
|
||||||
|
task={task}
|
||||||
|
source={source}
|
||||||
|
onSave={onSave}
|
||||||
|
onChangeID={onChangeID}
|
||||||
|
onChangeType={onChangeType}
|
||||||
|
onSelectDbrps={onSelectDbrps}
|
||||||
|
isNewTickscript={isNewTickscript}
|
||||||
|
/>
|
||||||
|
<div className="page-contents">
|
||||||
|
<div className="tickscript-console">
|
||||||
|
<div className="tickscript-console--output">
|
||||||
|
{validation
|
||||||
|
? <p>
|
||||||
|
{validation}
|
||||||
|
</p>
|
||||||
|
: <p className="tickscript-console--default">
|
||||||
|
Save your TICKscript to validate it
|
||||||
|
</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="tickscript-editor">
|
||||||
|
<TickscriptEditor
|
||||||
|
script={task.tickscript}
|
||||||
|
onChangeScript={onChangeScript}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
Tickscript.propTypes = {
|
||||||
|
onSave: func.isRequired,
|
||||||
|
source: shape(),
|
||||||
|
task: shape({
|
||||||
|
id: string,
|
||||||
|
script: string,
|
||||||
|
dbsrps: arrayOf(shape()),
|
||||||
|
}).isRequired,
|
||||||
|
onChangeScript: func.isRequired,
|
||||||
|
onSelectDbrps: func.isRequired,
|
||||||
|
validation: string,
|
||||||
|
onChangeType: func.isRequired,
|
||||||
|
onChangeID: func.isRequired,
|
||||||
|
isNewTickscript: bool.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Tickscript
|
|
@ -0,0 +1,36 @@
|
||||||
|
import React, {PropTypes, Component} from 'react'
|
||||||
|
import CodeMirror from '@skidding/react-codemirror'
|
||||||
|
import 'src/external/codemirror'
|
||||||
|
|
||||||
|
class TickscriptEditor extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCode = script => {
|
||||||
|
this.props.onChangeScript(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {script} = this.props
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
lineNumbers: true,
|
||||||
|
theme: 'material',
|
||||||
|
tabIndex: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CodeMirror value={script} onChange={this.updateCode} options={options} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {func, string} = PropTypes
|
||||||
|
|
||||||
|
TickscriptEditor.propTypes = {
|
||||||
|
onChangeScript: func,
|
||||||
|
script: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TickscriptEditor
|
|
@ -0,0 +1,66 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
import SourceIndicator from 'shared/components/SourceIndicator'
|
||||||
|
import TickscriptType from 'src/kapacitor/components/TickscriptType'
|
||||||
|
import MultiSelectDBDropdown from 'shared/components/MultiSelectDBDropdown'
|
||||||
|
import TickscriptID, {
|
||||||
|
TickscriptStaticID,
|
||||||
|
} from 'src/kapacitor/components/TickscriptID'
|
||||||
|
|
||||||
|
const addName = list => list.map(l => ({...l, name: `${l.db}.${l.rp}`}))
|
||||||
|
|
||||||
|
const TickscriptHeader = ({
|
||||||
|
task: {id, type, dbrps},
|
||||||
|
task,
|
||||||
|
source: {name},
|
||||||
|
onSave,
|
||||||
|
onChangeType,
|
||||||
|
onChangeID,
|
||||||
|
onSelectDbrps,
|
||||||
|
isNewTickscript,
|
||||||
|
}) =>
|
||||||
|
<div className="page-header">
|
||||||
|
<div className="page-header__container">
|
||||||
|
<div className="page-header__left">
|
||||||
|
{isNewTickscript
|
||||||
|
? <TickscriptID onChangeID={onChangeID} id={id} />
|
||||||
|
: <TickscriptStaticID id={task.name} />}
|
||||||
|
</div>
|
||||||
|
<div className="page-header__right">
|
||||||
|
<SourceIndicator sourceName={name} />
|
||||||
|
<TickscriptType type={type} onChangeType={onChangeType} />
|
||||||
|
<MultiSelectDBDropdown
|
||||||
|
selectedItems={addName(dbrps)}
|
||||||
|
onApply={onSelectDbrps}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="btn btn-success btn-sm"
|
||||||
|
title={id ? '' : 'ID your TICKscript to save'}
|
||||||
|
onClick={onSave}
|
||||||
|
disabled={!id}
|
||||||
|
>
|
||||||
|
Save Rule
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
TickscriptHeader.propTypes = {
|
||||||
|
onSave: func,
|
||||||
|
source: shape(),
|
||||||
|
onSelectDbrps: func.isRequired,
|
||||||
|
task: shape({
|
||||||
|
dbrps: arrayOf(
|
||||||
|
shape({
|
||||||
|
db: string,
|
||||||
|
rp: string,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
onChangeType: func.isRequired,
|
||||||
|
onChangeID: func.isRequired,
|
||||||
|
isNewTickscript: bool.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TickscriptHeader
|
|
@ -0,0 +1,44 @@
|
||||||
|
import React, {PropTypes, Component} from 'react'
|
||||||
|
|
||||||
|
class TickscriptID extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {onChangeID, id} = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
className="page-header--editing kapacitor-theme"
|
||||||
|
autoFocus={true}
|
||||||
|
value={id}
|
||||||
|
onChange={onChangeID}
|
||||||
|
placeholder="ID your TICKscript"
|
||||||
|
spellCheck={false}
|
||||||
|
autoComplete={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TickscriptStaticID = ({id}) =>
|
||||||
|
<h1
|
||||||
|
className="page-header--editing kapacitor-theme"
|
||||||
|
style={{display: 'flex', justifyContent: 'baseline'}}
|
||||||
|
>
|
||||||
|
{id}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
const {func, string} = PropTypes
|
||||||
|
|
||||||
|
TickscriptID.propTypes = {
|
||||||
|
onChangeID: func.isRequired,
|
||||||
|
id: string.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
TickscriptStaticID.propTypes = {
|
||||||
|
id: string.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TickscriptID
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, {PropTypes} from 'react'
|
||||||
|
const STREAM = 'stream'
|
||||||
|
const BATCH = 'batch'
|
||||||
|
|
||||||
|
const TickscriptType = ({type, onChangeType}) =>
|
||||||
|
<ul className="nav nav-tablist nav-tablist-sm">
|
||||||
|
<li
|
||||||
|
className={type === STREAM ? 'active' : ''}
|
||||||
|
onClick={onChangeType(STREAM)}
|
||||||
|
>
|
||||||
|
Stream
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
className={type === BATCH ? 'active' : ''}
|
||||||
|
onClick={onChangeType(BATCH)}
|
||||||
|
>
|
||||||
|
Batch
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
const {string, func} = PropTypes
|
||||||
|
|
||||||
|
TickscriptType.propTypes = {
|
||||||
|
type: string,
|
||||||
|
onChangeType: func,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TickscriptType
|
|
@ -1,8 +1,15 @@
|
||||||
export const KAPACITOR_RULES_TABLE = {
|
export const KAPACITOR_RULES_TABLE = {
|
||||||
colName: '200px',
|
colName: '200px',
|
||||||
colType: '90px',
|
colTrigger: '90px',
|
||||||
colMessage: '460px',
|
colMessage: '460px',
|
||||||
colAlerts: '120px',
|
colAlerts: '120px',
|
||||||
colEnabled: '64px',
|
colEnabled: '64px',
|
||||||
colActions: '176px',
|
colActions: '176px',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TASKS_TABLE = {
|
||||||
|
colID: '200px',
|
||||||
|
colType: '90px',
|
||||||
|
colEnabled: '64px',
|
||||||
|
colActions: '176px',
|
||||||
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ class KapacitorRulesPage extends Component {
|
||||||
this.state = {
|
this.state = {
|
||||||
hasKapacitor: false,
|
hasKapacitor: false,
|
||||||
loading: true,
|
loading: true,
|
||||||
tickscript: null,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,17 +37,9 @@ class KapacitorRulesPage extends Component {
|
||||||
actions.updateRuleStatusSuccess(rule.id, status)
|
actions.updateRuleStatusSuccess(rule.id, status)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReadTickscript = ({tickscript}) => () => {
|
|
||||||
this.setState({tickscript})
|
|
||||||
}
|
|
||||||
|
|
||||||
handleCloseTickscript = () => {
|
|
||||||
this.setState({tickscript: null})
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {source, rules} = this.props
|
const {source, rules} = this.props
|
||||||
const {hasKapacitor, loading, tickscript} = this.state
|
const {hasKapacitor, loading} = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KapacitorRules
|
<KapacitorRules
|
||||||
|
@ -58,9 +49,6 @@ class KapacitorRulesPage extends Component {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onDelete={this.handleDeleteRule}
|
onDelete={this.handleDeleteRule}
|
||||||
onChangeRuleStatus={this.handleRuleStatus}
|
onChangeRuleStatus={this.handleRuleStatus}
|
||||||
onReadTickscript={this.handleReadTickscript}
|
|
||||||
tickscript={tickscript}
|
|
||||||
onCloseTickscript={this.handleCloseTickscript}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import React, {PropTypes, Component} from 'react'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import {bindActionCreators} from 'redux'
|
||||||
|
|
||||||
|
import Tickscript from 'src/kapacitor/components/Tickscript'
|
||||||
|
import * as kapactiorActionCreators from 'src/kapacitor/actions/view'
|
||||||
|
import * as errorActionCreators from 'shared/actions/errors'
|
||||||
|
import {getActiveKapacitor} from 'src/shared/apis'
|
||||||
|
|
||||||
|
class TickscriptPage extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
kapacitor: {},
|
||||||
|
task: {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
status: 'enabled',
|
||||||
|
tickscript: '',
|
||||||
|
dbrps: [],
|
||||||
|
type: 'stream',
|
||||||
|
},
|
||||||
|
validation: '',
|
||||||
|
isEditingID: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
const {
|
||||||
|
source,
|
||||||
|
errorActions,
|
||||||
|
kapacitorActions,
|
||||||
|
params: {ruleID},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
const kapacitor = await getActiveKapacitor(source)
|
||||||
|
if (!kapacitor) {
|
||||||
|
errorActions.errorThrown(
|
||||||
|
'We could not find a configured Kapacitor for this source'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isEditing()) {
|
||||||
|
await kapacitorActions.getRule(kapacitor, ruleID)
|
||||||
|
const {id, name, tickscript, dbrps, type} = this.props.rules.find(
|
||||||
|
r => r.id === ruleID
|
||||||
|
)
|
||||||
|
|
||||||
|
this.setState({task: {tickscript, dbrps, type, status, name, id}})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({kapacitor})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSave = async () => {
|
||||||
|
const {kapacitor, task} = this.state
|
||||||
|
const {
|
||||||
|
source: {id: sourceID},
|
||||||
|
router,
|
||||||
|
kapacitorActions: {createTask, updateTask},
|
||||||
|
params: {ruleID},
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
let response
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this._isEditing()) {
|
||||||
|
response = await updateTask(kapacitor, task, ruleID, router, sourceID)
|
||||||
|
} else {
|
||||||
|
response = await createTask(kapacitor, task, router, sourceID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response && response.code === 500) {
|
||||||
|
return this.setState({validation: response.message})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeScript = tickscript => {
|
||||||
|
this.setState({task: {...this.state.task, tickscript}})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelectDbrps = dbrps => {
|
||||||
|
this.setState({task: {...this.state.task, dbrps}})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeType = type => () => {
|
||||||
|
this.setState({task: {...this.state.task, type}})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChangeID = e => {
|
||||||
|
this.setState({task: {...this.state.task, id: e.target.value}})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {source} = this.props
|
||||||
|
const {task, validation} = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tickscript
|
||||||
|
task={task}
|
||||||
|
source={source}
|
||||||
|
validation={validation}
|
||||||
|
onSave={this.handleSave}
|
||||||
|
isNewTickscript={!this._isEditing()}
|
||||||
|
onSelectDbrps={this.handleSelectDbrps}
|
||||||
|
onChangeScript={this.handleChangeScript}
|
||||||
|
onChangeType={this.handleChangeType}
|
||||||
|
onChangeID={this.handleChangeID}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_isEditing() {
|
||||||
|
const {params} = this.props
|
||||||
|
return params.ruleID && params.ruleID !== 'new'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {arrayOf, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
TickscriptPage.propTypes = {
|
||||||
|
source: shape({
|
||||||
|
name: string,
|
||||||
|
}),
|
||||||
|
errorActions: shape({
|
||||||
|
errorThrown: func.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
kapacitorActions: shape({
|
||||||
|
updateTask: func.isRequired,
|
||||||
|
createTask: func.isRequired,
|
||||||
|
getRule: func.isRequired,
|
||||||
|
}),
|
||||||
|
router: shape({
|
||||||
|
push: func.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
params: shape({
|
||||||
|
ruleID: string,
|
||||||
|
}).isRequired,
|
||||||
|
rules: arrayOf(shape()),
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
return {
|
||||||
|
rules: Object.values(state.rules),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
kapacitorActions: bindActionCreators(kapactiorActionCreators, dispatch),
|
||||||
|
errorActions: bindActionCreators(errorActionCreators, dispatch),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(TickscriptPage)
|
|
@ -2,9 +2,11 @@ import KapacitorPage from './containers/KapacitorPage'
|
||||||
import KapacitorRulePage from './containers/KapacitorRulePage'
|
import KapacitorRulePage from './containers/KapacitorRulePage'
|
||||||
import KapacitorRulesPage from './containers/KapacitorRulesPage'
|
import KapacitorRulesPage from './containers/KapacitorRulesPage'
|
||||||
import KapacitorTasksPage from './containers/KapacitorTasksPage'
|
import KapacitorTasksPage from './containers/KapacitorTasksPage'
|
||||||
|
import TickscriptPage from './containers/TickscriptPage'
|
||||||
export {
|
export {
|
||||||
KapacitorPage,
|
KapacitorPage,
|
||||||
KapacitorRulePage,
|
KapacitorRulePage,
|
||||||
KapacitorRulesPage,
|
KapacitorRulesPage,
|
||||||
KapacitorTasksPage,
|
KapacitorTasksPage,
|
||||||
|
TickscriptPage,
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ export function deleteSource(source) {
|
||||||
export function pingKapacitor(kapacitor) {
|
export function pingKapacitor(kapacitor) {
|
||||||
return AJAX({
|
return AJAX({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${kapacitor.links.proxy}?path=/kapacitor/v1/ping`,
|
url: kapacitor.links.ping,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React, {PropTypes, Component} from 'react'
|
||||||
|
|
||||||
|
import {showDatabases, showRetentionPolicies} from 'shared/apis/metaQuery'
|
||||||
|
import showDatabasesParser from 'shared/parsing/showDatabases'
|
||||||
|
import showRetentionPoliciesParser from 'shared/parsing/showRetentionPolicies'
|
||||||
|
import MultiSelectDropdown from 'shared/components/MultiSelectDropdown'
|
||||||
|
|
||||||
|
class MultiSelectDBDropdown extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
dbrps: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._getDbRps()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {dbrps} = this.state
|
||||||
|
const {onApply, selectedItems} = this.props
|
||||||
|
const label = 'Select databases'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultiSelectDropdown
|
||||||
|
label={label}
|
||||||
|
items={dbrps}
|
||||||
|
onApply={onApply}
|
||||||
|
isApplyShown={false}
|
||||||
|
selectedItems={selectedItems}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_getDbRps = async () => {
|
||||||
|
const {source: {links: {proxy}}} = this.context
|
||||||
|
const {onErrorThrown} = this.props
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {data} = await showDatabases(proxy)
|
||||||
|
const {databases, errors} = showDatabasesParser(data)
|
||||||
|
if (errors.length > 0) {
|
||||||
|
throw errors[0] // only one error can come back from this, but it's returned as an array
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await showRetentionPolicies(proxy, databases)
|
||||||
|
const dbrps = response.data.results.reduce((acc, result, i) => {
|
||||||
|
const {retentionPolicies} = showRetentionPoliciesParser(result)
|
||||||
|
const db = databases[i]
|
||||||
|
|
||||||
|
const rps = retentionPolicies.map(({name: rp}) => ({
|
||||||
|
db,
|
||||||
|
rp,
|
||||||
|
name: `${db}.${rp}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return [...acc, ...rps]
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
this.setState({dbrps})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
onErrorThrown(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const {arrayOf, func, shape, string} = PropTypes
|
||||||
|
|
||||||
|
MultiSelectDBDropdown.contextTypes = {
|
||||||
|
source: shape({
|
||||||
|
links: shape({
|
||||||
|
proxy: string.isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}).isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiSelectDBDropdown.propTypes = {
|
||||||
|
onErrorThrown: func,
|
||||||
|
onApply: func.isRequired,
|
||||||
|
selectedItems: arrayOf(shape()),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MultiSelectDBDropdown
|
|
@ -8,16 +8,19 @@ import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||||
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
|
import {DROPDOWN_MENU_MAX_HEIGHT} from 'shared/constants/index'
|
||||||
|
|
||||||
const labelText = ({localSelectedItems, isOpen, label}) => {
|
const labelText = ({localSelectedItems, isOpen, label}) => {
|
||||||
|
if (localSelectedItems.length) {
|
||||||
|
return localSelectedItems.map(s => s.name).join(', ')
|
||||||
|
}
|
||||||
|
|
||||||
if (label) {
|
if (label) {
|
||||||
return label
|
return label
|
||||||
} else if (localSelectedItems.length) {
|
|
||||||
return localSelectedItems.map(s => s).join(', ')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: be smarter about the text displayed here
|
// TODO: be smarter about the text displayed here
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
return '0 Selected'
|
return '0 Selected'
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'None'
|
return 'None'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,42 +30,52 @@ class MultiSelectDropdown extends Component {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
localSelectedItems: this.props.selectedItems,
|
localSelectedItems: props.selectedItems,
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onSelect = ::this.onSelect
|
|
||||||
this.onApplyFunctions = ::this.onApplyFunctions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleClickOutside() {
|
handleClickOutside() {
|
||||||
this.setState({isOpen: false})
|
this.setState({isOpen: false})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (!_.isEqual(this.props.selectedItems, nextProps.selectedItems)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({localSelectedItems: nextProps.selectedItems})
|
||||||
|
}
|
||||||
|
|
||||||
toggleMenu = e => {
|
toggleMenu = e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
this.setState({isOpen: !this.state.isOpen})
|
this.setState({isOpen: !this.state.isOpen})
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelect(item, e) {
|
onSelect = (item, e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
|
const {onApply, isApplyShown} = this.props
|
||||||
const {localSelectedItems} = this.state
|
const {localSelectedItems} = this.state
|
||||||
|
|
||||||
let nextItems
|
let nextItems
|
||||||
if (this.isSelected(item)) {
|
if (this.isSelected(item)) {
|
||||||
nextItems = localSelectedItems.filter(i => i !== item)
|
nextItems = localSelectedItems.filter(i => i.name !== item.name)
|
||||||
} else {
|
} else {
|
||||||
nextItems = [...localSelectedItems, item]
|
nextItems = [...localSelectedItems, item]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isApplyShown) {
|
||||||
|
onApply(nextItems)
|
||||||
|
}
|
||||||
|
|
||||||
this.setState({localSelectedItems: nextItems})
|
this.setState({localSelectedItems: nextItems})
|
||||||
}
|
}
|
||||||
|
|
||||||
isSelected(item) {
|
isSelected(item) {
|
||||||
return !!this.state.localSelectedItems.find(text => text === item)
|
return !!this.state.localSelectedItems.find(({name}) => name === item.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
onApplyFunctions(e) {
|
handleApply = e => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
|
||||||
this.setState({isOpen: false})
|
this.setState({isOpen: false})
|
||||||
|
@ -91,18 +104,18 @@ class MultiSelectDropdown extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMenu() {
|
renderMenu() {
|
||||||
const {items} = this.props
|
const {items, isApplyShown} = this.props
|
||||||
|
const applyButton = isApplyShown
|
||||||
return (
|
? <li className="multi-select--apply">
|
||||||
<ul className="dropdown-menu">
|
<button className="btn btn-xs btn-info" onClick={this.handleApply}>
|
||||||
<li className="multi-select--apply">
|
|
||||||
<button
|
|
||||||
className="btn btn-xs btn-info"
|
|
||||||
onClick={this.onApplyFunctions}
|
|
||||||
>
|
|
||||||
Apply
|
Apply
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
: null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="dropdown-menu">
|
||||||
|
{applyButton}
|
||||||
<FancyScrollbar
|
<FancyScrollbar
|
||||||
autoHide={false}
|
autoHide={false}
|
||||||
autoHeight={true}
|
autoHeight={true}
|
||||||
|
@ -119,7 +132,7 @@ class MultiSelectDropdown extends Component {
|
||||||
>
|
>
|
||||||
<div className="multi-select--checkbox" />
|
<div className="multi-select--checkbox" />
|
||||||
<span>
|
<span>
|
||||||
{listItem}
|
{listItem.name}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
|
@ -130,22 +143,34 @@ class MultiSelectDropdown extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {arrayOf, func, string} = PropTypes
|
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||||
|
|
||||||
MultiSelectDropdown.propTypes = {
|
MultiSelectDropdown.propTypes = {
|
||||||
onApply: func.isRequired,
|
onApply: func.isRequired,
|
||||||
items: arrayOf(string.isRequired).isRequired,
|
items: arrayOf(
|
||||||
selectedItems: arrayOf(string.isRequired).isRequired,
|
shape({
|
||||||
|
name: string.isRequired,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
|
selectedItems: arrayOf(
|
||||||
|
shape({
|
||||||
|
name: string.isRequired,
|
||||||
|
})
|
||||||
|
),
|
||||||
label: string,
|
label: string,
|
||||||
buttonSize: string,
|
buttonSize: string,
|
||||||
buttonColor: string,
|
buttonColor: string,
|
||||||
customClass: string,
|
customClass: string,
|
||||||
iconName: string,
|
iconName: string,
|
||||||
|
isApplyShown: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
MultiSelectDropdown.defaultProps = {
|
MultiSelectDropdown.defaultProps = {
|
||||||
buttonSize: 'btn-sm',
|
buttonSize: 'btn-sm',
|
||||||
buttonColor: 'btn-default',
|
buttonColor: 'btn-default',
|
||||||
customClass: 'dropdown-160',
|
customClass: 'dropdown-160',
|
||||||
|
selectedItems: [],
|
||||||
|
isApplyShown: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default OnClickOutside(MultiSelectDropdown)
|
export default OnClickOutside(MultiSelectDropdown)
|
||||||
|
|
|
@ -102,11 +102,9 @@ const SideNav = React.createClass({
|
||||||
link={`${sourcePrefix}/alerts`}
|
link={`${sourcePrefix}/alerts`}
|
||||||
>
|
>
|
||||||
<NavHeader link={`${sourcePrefix}/alerts`} title="Alerting" />
|
<NavHeader link={`${sourcePrefix}/alerts`} title="Alerting" />
|
||||||
<NavListItem link={`${sourcePrefix}/alerts`}>
|
<NavListItem link={`${sourcePrefix}/alerts`}>History</NavListItem>
|
||||||
Alert History
|
|
||||||
</NavListItem>
|
|
||||||
<NavListItem link={`${sourcePrefix}/alert-rules`}>
|
<NavListItem link={`${sourcePrefix}/alert-rules`}>
|
||||||
Alert Rules
|
Create
|
||||||
</NavListItem>
|
</NavListItem>
|
||||||
</NavBlock>
|
</NavBlock>
|
||||||
<NavBlock icon="crown2" link={`${sourcePrefix}/admin`}>
|
<NavBlock icon="crown2" link={`${sourcePrefix}/admin`}>
|
||||||
|
|
|
@ -11,11 +11,13 @@
|
||||||
@import 'theme/bootstrap-theme';
|
@import 'theme/bootstrap-theme';
|
||||||
@import 'theme/reset';
|
@import 'theme/reset';
|
||||||
|
|
||||||
// External
|
// Vendor
|
||||||
@import 'external/react-grid-layout';
|
@import 'external/react-grid-layout';
|
||||||
@import 'external/fixed-data-table-base';
|
@import 'external/fixed-data-table-base';
|
||||||
@import 'external/fixed-data-table-style';
|
@import 'external/fixed-data-table-style';
|
||||||
@import 'external/fixed-data-table';
|
@import 'external/fixed-data-table';
|
||||||
|
@import "external/codemirror";
|
||||||
|
@import "../../node_modules/codemirror/theme/material.css";
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
@import 'layout/page';
|
@import 'layout/page';
|
||||||
|
@ -27,6 +29,7 @@
|
||||||
// Components
|
// Components
|
||||||
@import 'components/ceo-display-options';
|
@import 'components/ceo-display-options';
|
||||||
@import 'components/confirm-buttons';
|
@import 'components/confirm-buttons';
|
||||||
|
@import 'components/code-mirror-theme';
|
||||||
@import 'components/custom-time-range';
|
@import 'components/custom-time-range';
|
||||||
@import 'components/dygraphs';
|
@import 'components/dygraphs';
|
||||||
@import 'components/fancy-scrollbars';
|
@import 'components/fancy-scrollbars';
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
Name: CHRONOGRAF YO
|
||||||
|
Author: Michael Kaminsky (http://github.com/mkaminsky11)
|
||||||
|
|
||||||
|
Original material color scheme by Mattia Astorino (https://github.com/equinusocio/material-theme)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
$tickscript-console-height: 120px;
|
||||||
|
|
||||||
|
.tickscript-console,
|
||||||
|
.tickscript-editor {
|
||||||
|
padding-left: $page-wrapper-padding;
|
||||||
|
padding-right: $page-wrapper-padding;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: $page-wrapper-max-width;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.tickscript-console {
|
||||||
|
height: $tickscript-console-height;
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
.tickscript-console--output {
|
||||||
|
padding: 0 60px;
|
||||||
|
font-family: $code-font;
|
||||||
|
font-weight: 600;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background-color: $g3-castle;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: $radius $radius 0 0;
|
||||||
|
|
||||||
|
> p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.tickscript-console--default {
|
||||||
|
color: $g10-wolf;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.tickscript-editor {
|
||||||
|
margin: 0 auto;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
height: calc(100% - #{$tickscript-console-height});
|
||||||
|
}
|
||||||
|
.ReactCodeMirror {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.cm-s-material.CodeMirror {
|
||||||
|
border-radius: 0 0 $radius $radius;
|
||||||
|
font-family: $code-font;
|
||||||
|
background-color: $g2-kevlar;
|
||||||
|
color: $c-neutrino;
|
||||||
|
font-weight: 600;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
@include custom-scrollbar-round($g2-kevlar,$g6-smoke);
|
||||||
|
}
|
||||||
|
.cm-s-material .CodeMirror-gutters {
|
||||||
|
background-color: fade-out($g4-onyx, 0.5);
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter.CodeMirror-linenumbers {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.cm-s-material.CodeMirror .CodeMirror-sizer {
|
||||||
|
margin-left: 60px;
|
||||||
|
}
|
||||||
|
.cm-s-material.CodeMirror .CodeMirror-linenumber.CodeMirror-gutter-elt {
|
||||||
|
padding-right: 9px;
|
||||||
|
width: 46px;
|
||||||
|
color: $g8-storm;
|
||||||
|
}
|
||||||
|
.cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: rgb(83,127,126); }
|
||||||
|
.cm-s-material .CodeMirror-cursor {
|
||||||
|
width: 2px;
|
||||||
|
border: 0;
|
||||||
|
background-color: $g20-white;
|
||||||
|
box-shadow:
|
||||||
|
0 0 3px $c-laser,
|
||||||
|
0 0 6px $c-ocean,
|
||||||
|
0 0 11px $c-amethyst;
|
||||||
|
}
|
||||||
|
.cm-s-material div.CodeMirror-selected,
|
||||||
|
.cm-s-material.CodeMirror-focused div.CodeMirror-selected {
|
||||||
|
background-color: fade-out($g8-storm,0.7);
|
||||||
|
}
|
||||||
|
.cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line > span::selection, .cm-s-material .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
|
||||||
|
.cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line > span::-moz-selection, .cm-s-material .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
|
||||||
|
|
||||||
|
.cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0); }
|
||||||
|
.cm-s-material .cm-keyword { color: $c-comet; }
|
||||||
|
.cm-s-material .cm-operator { color: $c-dreamsicle; }
|
||||||
|
.cm-s-material .cm-variable-2 { color: #80CBC4; }
|
||||||
|
.cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: $c-laser; }
|
||||||
|
.cm-s-material .cm-builtin { color: #DECB6B; }
|
||||||
|
.cm-s-material .cm-atom { color: $c-viridian; }
|
||||||
|
.cm-s-material .cm-number { color: $c-daisy; }
|
||||||
|
.cm-s-material .cm-def { color: rgba(233, 237, 237, 1); }
|
||||||
|
.cm-s-material .cm-string { color: $c-krypton; }
|
||||||
|
.cm-s-material .cm-string-2 { color: #80CBC4; }
|
||||||
|
.cm-s-material .cm-comment { color: $g10-wolf; }
|
||||||
|
.cm-s-material .cm-variable { color: $c-laser; }
|
||||||
|
.cm-s-material .cm-tag { color: #80CBC4; }
|
||||||
|
.cm-s-material .cm-meta { color: #80CBC4; }
|
||||||
|
.cm-s-material .cm-attribute { color: #FFCB6B; }
|
||||||
|
.cm-s-material .cm-property { color: #80CBAE; }
|
||||||
|
.cm-s-material .cm-qualifier { color: #DECB6B; }
|
||||||
|
.cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #DECB6B; }
|
||||||
|
.cm-s-material .cm-tag { color: rgba(255, 83, 112, 1); }
|
||||||
|
.cm-s-material .cm-error {
|
||||||
|
color: rgba(255, 255, 255, 1.0);
|
||||||
|
background-color: #EC5F67;
|
||||||
|
}
|
||||||
|
.cm-s-material .CodeMirror-matchingbracket {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: white !important;
|
||||||
|
}
|
|
@ -0,0 +1,340 @@
|
||||||
|
/* BASICS */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
/* Set height, width, borders, and global font properties here */
|
||||||
|
font-family: monospace;
|
||||||
|
height: calc(100vh - 140px);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PADDING */
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
padding: 4px 0; /* Vertical padding around content */
|
||||||
|
}
|
||||||
|
.CodeMirror pre {
|
||||||
|
padding: 0 4px; /* Horizontal padding of content */
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
background-color: white; /* The little square between H and V scrollbars */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GUTTER */
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #f7f7f7;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.CodeMirror-linenumbers {}
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
padding: 0 3px 0 5px;
|
||||||
|
min-width: 20px;
|
||||||
|
text-align: right;
|
||||||
|
color: #999;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-guttermarker { color: black; }
|
||||||
|
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||||
|
|
||||||
|
/* CURSOR */
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
border-left: 1px solid black;
|
||||||
|
border-right: none;
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
/* Shown when moving in bi-directional text */
|
||||||
|
.CodeMirror div.CodeMirror-secondarycursor {
|
||||||
|
border-left: 1px solid silver;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor .CodeMirror-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0 !important;
|
||||||
|
background: #7e7;
|
||||||
|
}
|
||||||
|
.cm-fat-cursor div.CodeMirror-cursors {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-animate-fat-cursor {
|
||||||
|
width: auto;
|
||||||
|
border: 0;
|
||||||
|
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||||
|
-moz-animation: blink 1.06s steps(1) infinite;
|
||||||
|
animation: blink 1.06s steps(1) infinite;
|
||||||
|
background-color: #7e7;
|
||||||
|
}
|
||||||
|
@-moz-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@-webkit-keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
@keyframes blink {
|
||||||
|
0% {}
|
||||||
|
50% { background-color: transparent; }
|
||||||
|
100% {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Can style cursor different in overwrite (non-insert) mode */
|
||||||
|
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||||
|
|
||||||
|
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||||
|
|
||||||
|
.CodeMirror-rulers {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: -50px; bottom: -20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.CodeMirror-ruler {
|
||||||
|
border-left: 1px solid #ccc;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DEFAULT THEME */
|
||||||
|
|
||||||
|
.cm-s-default .cm-header {color: blue;}
|
||||||
|
.cm-s-default .cm-quote {color: #090;}
|
||||||
|
.cm-negative {color: #d44;}
|
||||||
|
.cm-positive {color: #292;}
|
||||||
|
.cm-header, .cm-strong {font-weight: bold;}
|
||||||
|
.cm-em {font-style: italic;}
|
||||||
|
.cm-link {text-decoration: underline;}
|
||||||
|
.cm-strikethrough {text-decoration: line-through;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-keyword {color: #708;}
|
||||||
|
.cm-s-default .cm-atom {color: #219;}
|
||||||
|
.cm-s-default .cm-number {color: #164;}
|
||||||
|
.cm-s-default .cm-def {color: #00f;}
|
||||||
|
.cm-s-default .cm-variable,
|
||||||
|
.cm-s-default .cm-punctuation,
|
||||||
|
.cm-s-default .cm-property,
|
||||||
|
.cm-s-default .cm-operator {}
|
||||||
|
.cm-s-default .cm-variable-2 {color: #05a;}
|
||||||
|
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
|
||||||
|
.cm-s-default .cm-comment {color: #a50;}
|
||||||
|
.cm-s-default .cm-string {color: #a11;}
|
||||||
|
.cm-s-default .cm-string-2 {color: #f50;}
|
||||||
|
.cm-s-default .cm-meta {color: #555;}
|
||||||
|
.cm-s-default .cm-qualifier {color: #555;}
|
||||||
|
.cm-s-default .cm-builtin {color: #30a;}
|
||||||
|
.cm-s-default .cm-bracket {color: #997;}
|
||||||
|
.cm-s-default .cm-tag {color: #170;}
|
||||||
|
.cm-s-default .cm-attribute {color: #00c;}
|
||||||
|
.cm-s-default .cm-hr {color: #999;}
|
||||||
|
.cm-s-default .cm-link {color: #00c;}
|
||||||
|
|
||||||
|
.cm-s-default .cm-error {color: #f00;}
|
||||||
|
.cm-invalidchar {color: #f00;}
|
||||||
|
|
||||||
|
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||||
|
|
||||||
|
/* Default styles for common addons */
|
||||||
|
|
||||||
|
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
|
||||||
|
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
|
||||||
|
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||||
|
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||||
|
|
||||||
|
/* STOP */
|
||||||
|
|
||||||
|
/* The rest of this file contains styles related to the mechanics of
|
||||||
|
the editor. You probably shouldn't touch them. */
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-scroll {
|
||||||
|
overflow: scroll !important; /* Things will break if this is overridden */
|
||||||
|
/* 30px is the magic margin used to hide the element's real scrollbars */
|
||||||
|
/* See overflow: hidden in .CodeMirror */
|
||||||
|
margin-bottom: -30px; margin-right: -30px;
|
||||||
|
padding-bottom: 30px;
|
||||||
|
height: 100%;
|
||||||
|
outline: none; /* Prevent dragging from highlighting the element */
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.CodeMirror-sizer {
|
||||||
|
position: relative;
|
||||||
|
border-right: 30px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||||
|
before actual scrolling happens, thus preventing shaking and
|
||||||
|
flickering artifacts. */
|
||||||
|
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 6;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-vscrollbar {
|
||||||
|
right: 0; top: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-hscrollbar {
|
||||||
|
bottom: 0; left: 0;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: scroll;
|
||||||
|
}
|
||||||
|
.CodeMirror-scrollbar-filler {
|
||||||
|
right: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-filler {
|
||||||
|
left: 0; bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-gutters {
|
||||||
|
position: absolute; left: 0; top: 0;
|
||||||
|
min-height: 100%;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter {
|
||||||
|
white-space: normal;
|
||||||
|
height: 100%;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
margin-bottom: -30px;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 4;
|
||||||
|
background: none !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-background {
|
||||||
|
position: absolute;
|
||||||
|
top: 0; bottom: 0;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-elt {
|
||||||
|
position: absolute;
|
||||||
|
cursor: default;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||||
|
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||||
|
|
||||||
|
.CodeMirror-lines {
|
||||||
|
cursor: text;
|
||||||
|
min-height: 1px; /* prevents collapsing before first draw */
|
||||||
|
}
|
||||||
|
.CodeMirror pre {
|
||||||
|
/* Reset some styles that the rest of the page might have set */
|
||||||
|
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||||
|
border-width: 0;
|
||||||
|
background: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
margin: 0;
|
||||||
|
white-space: pre;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
z-index: 2;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
-webkit-font-variant-ligatures: contextual;
|
||||||
|
font-variant-ligatures: contextual;
|
||||||
|
}
|
||||||
|
.CodeMirror-wrap pre {
|
||||||
|
word-wrap: break-word;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linebackground {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; right: 0; top: 0; bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-linewidget {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-widget {}
|
||||||
|
|
||||||
|
.CodeMirror-rtl pre { direction: rtl; }
|
||||||
|
|
||||||
|
.CodeMirror-code {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force content-box sizing for the elements where we expect it */
|
||||||
|
.CodeMirror-scroll,
|
||||||
|
.CodeMirror-sizer,
|
||||||
|
.CodeMirror-gutter,
|
||||||
|
.CodeMirror-gutters,
|
||||||
|
.CodeMirror-linenumber {
|
||||||
|
-moz-box-sizing: content-box;
|
||||||
|
box-sizing: content-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-measure {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-cursor {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
.CodeMirror-measure pre { position: static; }
|
||||||
|
|
||||||
|
div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
position: relative;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
div.CodeMirror-dragcursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused div.CodeMirror-cursors {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-selected { background: #d9d9d9; }
|
||||||
|
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||||
|
.CodeMirror-crosshair { cursor: crosshair; }
|
||||||
|
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||||
|
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||||
|
|
||||||
|
.cm-searching {
|
||||||
|
background: #ffa;
|
||||||
|
background: rgba(255, 255, 0, .4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Used to force a border model for a node */
|
||||||
|
.cm-force-border { padding-right: .1px; }
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
/* Hide the cursor when printing */
|
||||||
|
.CodeMirror div.CodeMirror-cursors {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* See issue #2901 */
|
||||||
|
.cm-tab-wrap-hack:after { content: ''; }
|
||||||
|
|
||||||
|
/* Help users use markselection to safely style text background */
|
||||||
|
span.CodeMirror-selectedtext { background: none; }
|
50
ui/yarn.lock
50
ui/yarn.lock
|
@ -98,6 +98,16 @@
|
||||||
webpack-dev-middleware "^1.6.0"
|
webpack-dev-middleware "^1.6.0"
|
||||||
webpack-hot-middleware "^2.13.2"
|
webpack-hot-middleware "^2.13.2"
|
||||||
|
|
||||||
|
"@skidding/react-codemirror@^1.0.1":
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@skidding/react-codemirror/-/react-codemirror-1.0.1.tgz#ce7927a10248e2369f8bce03669b92d88fead797"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
codemirror "^5.18.2"
|
||||||
|
create-react-class "^15.5.1"
|
||||||
|
lodash.isequal "^4.5.0"
|
||||||
|
prop-types "^15.5.4"
|
||||||
|
|
||||||
abab@^1.0.0:
|
abab@^1.0.0:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
|
resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d"
|
||||||
|
@ -1794,6 +1804,10 @@ code-point-at@^1.0.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
|
||||||
|
|
||||||
|
codemirror@^5.18.2:
|
||||||
|
version "5.28.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.28.0.tgz#2978d9280d671351a4f5737d06bbd681a0fd6f83"
|
||||||
|
|
||||||
color-convert@^1.3.0:
|
color-convert@^1.3.0:
|
||||||
version "1.8.2"
|
version "1.8.2"
|
||||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.8.2.tgz#be868184d7c8631766d54e7078e2672d7c7e3339"
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.8.2.tgz#be868184d7c8631766d54e7078e2672d7c7e3339"
|
||||||
|
@ -4402,6 +4416,10 @@ lodash.debounce@^3.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash._getnative "^3.0.0"
|
lodash._getnative "^3.0.0"
|
||||||
|
|
||||||
|
lodash.debounce@^4.0.8:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
|
||||||
lodash.deburr@^3.0.0:
|
lodash.deburr@^3.0.0:
|
||||||
version "3.2.0"
|
version "3.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5"
|
resolved "https://registry.yarnpkg.com/lodash.deburr/-/lodash.deburr-3.2.0.tgz#6da8f54334a366a7cf4c4c76ef8d80aa1b365ed5"
|
||||||
|
@ -4462,6 +4480,10 @@ lodash.isequal@^4.0.0:
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.4.0.tgz#6295768e98e14dc15ce8d362ef6340db82852031"
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.4.0.tgz#6295768e98e14dc15ce8d362ef6340db82852031"
|
||||||
|
|
||||||
|
lodash.isequal@^4.5.0:
|
||||||
|
version "4.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
|
||||||
|
|
||||||
lodash.keys@^3.0.0, lodash.keys@^3.1.2:
|
lodash.keys@^3.0.0, lodash.keys@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a"
|
||||||
|
@ -4559,6 +4581,12 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
js-tokens "^2.0.0"
|
js-tokens "^2.0.0"
|
||||||
|
|
||||||
|
loose-envify@^1.3.1:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
|
||||||
|
dependencies:
|
||||||
|
js-tokens "^3.0.0"
|
||||||
|
|
||||||
loud-rejection@^1.0.0:
|
loud-rejection@^1.0.0:
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||||
|
@ -5814,6 +5842,13 @@ promise@^7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
asap "~2.0.3"
|
asap "~2.0.3"
|
||||||
|
|
||||||
|
prop-types@^15.5.4:
|
||||||
|
version "15.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154"
|
||||||
|
dependencies:
|
||||||
|
fbjs "^0.8.9"
|
||||||
|
loose-envify "^1.3.1"
|
||||||
|
|
||||||
prop-types@^15.5.6, prop-types@^15.5.8:
|
prop-types@^15.5.6, prop-types@^15.5.8:
|
||||||
version "15.5.8"
|
version "15.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.8.tgz#6b7b2e141083be38c8595aa51fc55775c7199394"
|
||||||
|
@ -5932,6 +5967,21 @@ react-addons-test-utils@^15.0.2:
|
||||||
version "15.4.1"
|
version "15.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.4.1.tgz#1e4caab151bf27cce26df5f9cb714f4fd8359ae1"
|
resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.4.1.tgz#1e4caab151bf27cce26df5f9cb714f4fd8359ae1"
|
||||||
|
|
||||||
|
react-addons-update@^15.1.0:
|
||||||
|
version "15.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-addons-update/-/react-addons-update-15.4.1.tgz#00c07f45243aa9715e1706bbfd1f23d3d8d80bd1"
|
||||||
|
|
||||||
|
react-codemirror@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-1.0.0.tgz#91467b53b1f5d80d916a2fd0b4c7adb85a9001ba"
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
codemirror "^5.18.2"
|
||||||
|
create-react-class "^15.5.1"
|
||||||
|
lodash.debounce "^4.0.8"
|
||||||
|
lodash.isequal "^4.5.0"
|
||||||
|
prop-types "^15.5.4"
|
||||||
|
|
||||||
react-custom-scrollbars@^4.1.1:
|
react-custom-scrollbars@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.1.1.tgz#cf08cd43b1297ab11e6fcc5c9a800e7b70b6f248"
|
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.1.1.tgz#cf08cd43b1297ab11e6fcc5c9a800e7b70b6f248"
|
||||||
|
|
Loading…
Reference in New Issue