Merge pull request #1721 from influxdata/feature/tickscript-editor
TICKscript Editorpull/10616/head
commit
56dcc31d82
|
@ -9,6 +9,7 @@
|
|||
### Features
|
||||
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. [#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
|
||||
|
||||
### UI Improvements
|
||||
|
@ -157,6 +158,8 @@
|
|||
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. [#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
|
||||
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
|
||||
Self string `json:"self"` // Self link mapping to this resource
|
||||
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 {
|
||||
|
@ -108,6 +110,8 @@ func newKapacitor(srv chronograf.Server) kapacitor {
|
|||
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),
|
||||
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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@skidding/react-codemirror": "^1.0.1",
|
||||
"axios": "^0.13.1",
|
||||
"bignumber.js": "^4.0.2",
|
||||
"bootstrap": "^3.3.7",
|
||||
|
@ -112,6 +113,7 @@
|
|||
"query-string": "^5.0.0",
|
||||
"react": "^15.0.2",
|
||||
"react-addons-shallow-compare": "^15.0.2",
|
||||
"react-codemirror": "^1.0.0",
|
||||
"react-custom-scrollbars": "^4.1.1",
|
||||
"react-dimensions": "^1.2.0",
|
||||
"react-dom": "^15.0.2",
|
||||
|
|
|
@ -10,7 +10,7 @@ import DeleteConfirmTableCell from 'shared/components/DeleteConfirmTableCell'
|
|||
import {ROLES_TABLE} from 'src/admin/constants/tableSizing'
|
||||
|
||||
const RoleRow = ({
|
||||
role: {name, permissions, users},
|
||||
role: {name: roleName, permissions, users = []},
|
||||
role,
|
||||
allUsers,
|
||||
allPermissions,
|
||||
|
@ -23,12 +23,14 @@ const RoleRow = ({
|
|||
onUpdateRoleUsers,
|
||||
onUpdateRolePermissions,
|
||||
}) => {
|
||||
const handleUpdateUsers = u => {
|
||||
onUpdateRoleUsers(role, u.map(n => ({name: n})))
|
||||
const handleUpdateUsers = usrs => {
|
||||
onUpdateRoleUsers(role, usrs)
|
||||
}
|
||||
|
||||
const handleUpdatePermissions = allowed => {
|
||||
onUpdateRolePermissions(role, [{scope: 'all', allowed}])
|
||||
onUpdateRolePermissions(role, [
|
||||
{scope: 'all', allowed: allowed.map(({name}) => name)},
|
||||
])
|
||||
}
|
||||
|
||||
const perms = _.get(permissions, ['0', 'allowed'], [])
|
||||
|
@ -62,13 +64,13 @@ const RoleRow = ({
|
|||
return (
|
||||
<tr>
|
||||
<td style={{width: `${ROLES_TABLE.colName}px`}}>
|
||||
{name}
|
||||
{roleName}
|
||||
</td>
|
||||
<td>
|
||||
{allPermissions && allPermissions.length
|
||||
? <MultiSelectDropdown
|
||||
items={allPermissions}
|
||||
selectedItems={perms}
|
||||
items={allPermissions.map(name => ({name}))}
|
||||
selectedItems={perms.map(name => ({name}))}
|
||||
label={perms.length ? '' : 'Select Permissions'}
|
||||
onApply={handleUpdatePermissions}
|
||||
buttonSize="btn-xs"
|
||||
|
@ -85,9 +87,9 @@ const RoleRow = ({
|
|||
<td>
|
||||
{allUsers && allUsers.length
|
||||
? <MultiSelectDropdown
|
||||
items={allUsers.map(u => u.name)}
|
||||
selectedItems={users === undefined ? [] : users.map(u => u.name)}
|
||||
label={users && users.length ? '' : 'Select Users'}
|
||||
items={allUsers}
|
||||
selectedItems={users}
|
||||
label={users.length ? '' : 'Select Users'}
|
||||
onApply={handleUpdateUsers}
|
||||
buttonSize="btn-xs"
|
||||
buttonColor="btn-primary"
|
||||
|
|
|
@ -12,7 +12,7 @@ import ChangePassRow from 'src/admin/components/ChangePassRow'
|
|||
import {USERS_TABLE} from 'src/admin/constants/tableSizing'
|
||||
|
||||
const UserRow = ({
|
||||
user: {name, roles, permissions, password},
|
||||
user: {name, roles = [], permissions, password},
|
||||
user,
|
||||
allRoles,
|
||||
allPermissions,
|
||||
|
@ -27,14 +27,15 @@ const UserRow = ({
|
|||
onUpdateRoles,
|
||||
onUpdatePassword,
|
||||
}) => {
|
||||
const handleUpdatePermissions = allowed => {
|
||||
const handleUpdatePermissions = perms => {
|
||||
const allowed = perms.map(p => p.name)
|
||||
onUpdatePermissions(user, [{scope: 'all', allowed}])
|
||||
}
|
||||
|
||||
const handleUpdateRoles = roleNames => {
|
||||
onUpdateRoles(
|
||||
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)
|
||||
}
|
||||
|
||||
const perms = _.get(permissions, ['0', 'allowed'], [])
|
||||
|
||||
if (isEditing) {
|
||||
return (
|
||||
<tr className="admin-table--edit-row">
|
||||
|
@ -85,13 +88,9 @@ const UserRow = ({
|
|||
{hasRoles
|
||||
? <td>
|
||||
<MultiSelectDropdown
|
||||
items={allRoles.map(r => r.name)}
|
||||
selectedItems={
|
||||
roles
|
||||
? roles.map(r => r.name)
|
||||
: [] /* TODO remove check when server returns empty list */
|
||||
}
|
||||
label={roles && roles.length ? '' : 'Select Roles'}
|
||||
items={allRoles}
|
||||
selectedItems={roles.map(r => ({name: r.name}))}
|
||||
label={roles.length ? '' : 'Select Roles'}
|
||||
onApply={handleUpdateRoles}
|
||||
buttonSize="btn-xs"
|
||||
buttonColor="btn-primary"
|
||||
|
@ -104,8 +103,8 @@ const UserRow = ({
|
|||
<td>
|
||||
{allPermissions && allPermissions.length
|
||||
? <MultiSelectDropdown
|
||||
items={allPermissions}
|
||||
selectedItems={_.get(permissions, ['0', 'allowed'], [])}
|
||||
items={allPermissions.map(p => ({name: p}))}
|
||||
selectedItems={perms.map(p => ({name: p}))}
|
||||
label={
|
||||
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,
|
||||
KapacitorRulesPage,
|
||||
KapacitorTasksPage,
|
||||
TickscriptPage,
|
||||
} from 'src/kapacitor'
|
||||
import {AdminPage} from 'src/admin'
|
||||
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/:ruleID" 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/:id/edit" component={KapacitorPage} />
|
||||
<Route path="kapacitor-tasks" component={KapacitorTasksPage} />
|
||||
|
|
|
@ -3,9 +3,11 @@ import {getActiveKapacitor} from 'shared/apis'
|
|||
import {publishNotification} from 'shared/actions/notifications'
|
||||
import {
|
||||
getRules,
|
||||
getRule,
|
||||
getRule as getRuleAJAX,
|
||||
deleteRule as deleteRuleAPI,
|
||||
updateRuleStatus as updateRuleStatusAPI,
|
||||
createTask as createTaskAJAX,
|
||||
updateTask as updateTaskAJAX,
|
||||
} from 'src/kapacitor/apis'
|
||||
import {errorThrown} from 'shared/actions/errors'
|
||||
|
||||
|
@ -19,7 +21,7 @@ const loadQuery = query => ({
|
|||
export function fetchRule(source, ruleID) {
|
||||
return dispatch => {
|
||||
getActiveKapacitor(source).then(kapacitor => {
|
||||
getRule(kapacitor, ruleID).then(({data: rule}) => {
|
||||
getRuleAJAX(kapacitor, ruleID).then(({data: rule}) => {
|
||||
dispatch({
|
||||
type: 'LOAD_RULE',
|
||||
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() {
|
||||
return dispatch => {
|
||||
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'
|
||||
|
||||
function rangeRule(rule) {
|
||||
const rangeRule = rule => {
|
||||
const {value, rangeValue, operator} = rule.values
|
||||
|
||||
if (operator === 'inside range' || operator === 'outside range') {
|
||||
|
@ -11,7 +11,7 @@ function rangeRule(rule) {
|
|||
return rule
|
||||
}
|
||||
|
||||
export function createRule(kapacitor, rule) {
|
||||
export const createRule = (kapacitor, rule) => {
|
||||
return AJAX({
|
||||
method: 'POST',
|
||||
url: kapacitor.links.rules,
|
||||
|
@ -19,21 +19,26 @@ export function createRule(kapacitor, rule) {
|
|||
})
|
||||
}
|
||||
|
||||
export function getRules(kapacitor) {
|
||||
export const getRules = kapacitor => {
|
||||
return AJAX({
|
||||
method: 'GET',
|
||||
url: kapacitor.links.rules,
|
||||
})
|
||||
}
|
||||
|
||||
export function getRule(kapacitor, ruleID) {
|
||||
return AJAX({
|
||||
method: 'GET',
|
||||
url: `${kapacitor.links.rules}/${ruleID}`,
|
||||
})
|
||||
export const getRule = async (kapacitor, ruleID) => {
|
||||
try {
|
||||
return await AJAX({
|
||||
method: 'GET',
|
||||
url: `${kapacitor.links.rules}/${ruleID}`,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function editRule(rule) {
|
||||
export const editRule = rule => {
|
||||
return AJAX({
|
||||
method: 'PUT',
|
||||
url: rule.links.self,
|
||||
|
@ -41,17 +46,57 @@ export function editRule(rule) {
|
|||
})
|
||||
}
|
||||
|
||||
export function deleteRule(rule) {
|
||||
export const deleteRule = rule => {
|
||||
return AJAX({
|
||||
method: 'DELETE',
|
||||
url: rule.links.self,
|
||||
})
|
||||
}
|
||||
|
||||
export function updateRuleStatus(rule, status) {
|
||||
export const updateRuleStatus = (rule, status) => {
|
||||
return AJAX({
|
||||
method: 'PATCH',
|
||||
url: rule.links.self,
|
||||
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 SourceIndicator from 'shared/components/SourceIndicator'
|
||||
import KapacitorRulesTable from 'src/kapacitor/components/KapacitorRulesTable'
|
||||
import TasksTable from 'src/kapacitor/components/TasksTable'
|
||||
import FancyScrollbar from 'shared/components/FancyScrollbar'
|
||||
import TICKscriptOverlay from 'src/kapacitor/components/TICKscriptOverlay'
|
||||
|
||||
const KapacitorRules = ({
|
||||
source,
|
||||
|
@ -13,10 +13,7 @@ const KapacitorRules = ({
|
|||
hasKapacitor,
|
||||
loading,
|
||||
onDelete,
|
||||
tickscript,
|
||||
onChangeRuleStatus,
|
||||
onReadTickscript,
|
||||
onCloseTickscript,
|
||||
}) => {
|
||||
if (loading) {
|
||||
return (
|
||||
|
@ -44,43 +41,72 @@ const KapacitorRules = ({
|
|||
)
|
||||
}
|
||||
|
||||
const tableHeader =
|
||||
rules.length === 1 ? '1 Alert Rule' : `${rules.length} Alert Rules`
|
||||
const rulez = rules.filter(r => r.query)
|
||||
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 (
|
||||
<PageContents
|
||||
source={source}
|
||||
tickscript={tickscript}
|
||||
onReadTickscript={onReadTickscript}
|
||||
onCloseTickscript={onCloseTickscript}
|
||||
>
|
||||
<PageContents source={source}>
|
||||
<div className="panel-heading u-flex u-ai-center u-jc-space-between">
|
||||
<h2 className="panel-title">
|
||||
{tableHeader}
|
||||
{rHeader}
|
||||
</h2>
|
||||
<Link
|
||||
to={`/sources/${source.id}/alert-rules/new`}
|
||||
className="btn btn-sm btn-primary"
|
||||
>
|
||||
Create Rule
|
||||
</Link>
|
||||
<div className="u-flex u-ai-center u-jc-space-between">
|
||||
<Link
|
||||
to={`/sources/${source.id}/alert-rules/new`}
|
||||
className="btn btn-sm btn-primary"
|
||||
style={{marginRight: '4px'}}
|
||||
>
|
||||
Build Rule
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<KapacitorRulesTable
|
||||
source={source}
|
||||
rules={rules}
|
||||
rules={rulez}
|
||||
onDelete={onDelete}
|
||||
onReadTickscript={onReadTickscript}
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
const PageContents = ({children, source, tickscript, onCloseTickscript}) =>
|
||||
const PageContents = ({children, source}) =>
|
||||
<div className="page">
|
||||
<div className="page-header">
|
||||
<div className="page-header__container">
|
||||
<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 className="page-header__right">
|
||||
<SourceIndicator sourceName={source && source.name} />
|
||||
|
@ -98,15 +124,9 @@ const PageContents = ({children, source, tickscript, onCloseTickscript}) =>
|
|||
</div>
|
||||
</div>
|
||||
</FancyScrollbar>
|
||||
{tickscript
|
||||
? <TICKscriptOverlay
|
||||
tickscript={tickscript}
|
||||
onClose={onCloseTickscript}
|
||||
/>
|
||||
: null}
|
||||
</div>
|
||||
|
||||
const {arrayOf, bool, func, node, shape, string} = PropTypes
|
||||
const {arrayOf, bool, func, node, shape} = PropTypes
|
||||
|
||||
KapacitorRules.propTypes = {
|
||||
source: shape(),
|
||||
|
@ -115,15 +135,11 @@ KapacitorRules.propTypes = {
|
|||
loading: bool,
|
||||
onChangeRuleStatus: func,
|
||||
onDelete: func,
|
||||
tickscript: string,
|
||||
onReadTickscript: func,
|
||||
onCloseTickscript: func,
|
||||
}
|
||||
|
||||
PageContents.propTypes = {
|
||||
children: node,
|
||||
source: shape(),
|
||||
tickscript: string,
|
||||
onCloseTickscript: func,
|
||||
}
|
||||
|
||||
|
|
|
@ -5,26 +5,20 @@ import _ from 'lodash'
|
|||
import {KAPACITOR_RULES_TABLE} from 'src/kapacitor/constants/tableSizing'
|
||||
const {
|
||||
colName,
|
||||
colType,
|
||||
colTrigger,
|
||||
colMessage,
|
||||
colAlerts,
|
||||
colEnabled,
|
||||
colActions,
|
||||
} = KAPACITOR_RULES_TABLE
|
||||
|
||||
const KapacitorRulesTable = ({
|
||||
rules,
|
||||
source,
|
||||
onDelete,
|
||||
onReadTickscript,
|
||||
onChangeRuleStatus,
|
||||
}) =>
|
||||
const KapacitorRulesTable = ({rules, source, onDelete, onChangeRuleStatus}) =>
|
||||
<div className="panel-body">
|
||||
<table className="table v-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<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: colAlerts}}>Alerts</th>
|
||||
<th style={{width: colEnabled}} className="text-center">
|
||||
|
@ -42,7 +36,6 @@ const KapacitorRulesTable = ({
|
|||
source={source}
|
||||
onDelete={onDelete}
|
||||
onChangeRuleStatus={onChangeRuleStatus}
|
||||
onRead={onReadTickscript}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
|
@ -50,12 +43,14 @@ const KapacitorRulesTable = ({
|
|||
</table>
|
||||
</div>
|
||||
|
||||
const RuleRow = ({rule, source, onRead, onDelete, onChangeRuleStatus}) =>
|
||||
const handleDelete = (rule, onDelete) => onDelete(rule)
|
||||
|
||||
const RuleRow = ({rule, source, onDelete, onChangeRuleStatus}) =>
|
||||
<tr key={rule.id}>
|
||||
<td style={{width: colName}} className="monotype">
|
||||
<RuleTitle rule={rule} source={source} />
|
||||
</td>
|
||||
<td style={{width: colType}} className="monotype">
|
||||
<td style={{width: colTrigger}} className="monotype">
|
||||
{rule.trigger}
|
||||
</td>
|
||||
<td className="monotype">
|
||||
|
@ -82,10 +77,16 @@ const RuleRow = ({rule, source, onRead, onDelete, onChangeRuleStatus}) =>
|
|||
</div>
|
||||
</td>
|
||||
<td style={{width: colActions}} className="text-right table-cell-nowrap">
|
||||
<button className="btn btn-info btn-xs" onClick={onRead(rule)}>
|
||||
View TICKscript
|
||||
</button>
|
||||
<button className="btn btn-danger btn-xs" onClick={onDelete(rule)}>
|
||||
<Link
|
||||
className="btn btn-info btn-xs"
|
||||
to={`/sources/${source.id}/tickscript/${rule.id}`}
|
||||
>
|
||||
Edit TICKscript
|
||||
</Link>
|
||||
<button
|
||||
className="btn btn-danger btn-xs"
|
||||
onClick={handleDelete(rule, onDelete)}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</td>
|
||||
|
@ -117,7 +118,6 @@ KapacitorRulesTable.propTypes = {
|
|||
source: shape({
|
||||
id: string.isRequired,
|
||||
}).isRequired,
|
||||
onReadTickscript: func,
|
||||
}
|
||||
|
||||
RuleRow.propTypes = {
|
||||
|
@ -125,7 +125,6 @@ RuleRow.propTypes = {
|
|||
source: shape(),
|
||||
onChangeRuleStatus: func,
|
||||
onDelete: func,
|
||||
onRead: func,
|
||||
}
|
||||
|
||||
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 = {
|
||||
colName: '200px',
|
||||
colType: '90px',
|
||||
colTrigger: '90px',
|
||||
colMessage: '460px',
|
||||
colAlerts: '120px',
|
||||
colEnabled: '64px',
|
||||
colActions: '176px',
|
||||
}
|
||||
|
||||
export const TASKS_TABLE = {
|
||||
colID: '200px',
|
||||
colType: '90px',
|
||||
colEnabled: '64px',
|
||||
colActions: '176px',
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ class KapacitorRulesPage extends Component {
|
|||
this.state = {
|
||||
hasKapacitor: false,
|
||||
loading: true,
|
||||
tickscript: null,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,17 +37,9 @@ class KapacitorRulesPage extends Component {
|
|||
actions.updateRuleStatusSuccess(rule.id, status)
|
||||
}
|
||||
|
||||
handleReadTickscript = ({tickscript}) => () => {
|
||||
this.setState({tickscript})
|
||||
}
|
||||
|
||||
handleCloseTickscript = () => {
|
||||
this.setState({tickscript: null})
|
||||
}
|
||||
|
||||
render() {
|
||||
const {source, rules} = this.props
|
||||
const {hasKapacitor, loading, tickscript} = this.state
|
||||
const {hasKapacitor, loading} = this.state
|
||||
|
||||
return (
|
||||
<KapacitorRules
|
||||
|
@ -58,9 +49,6 @@ class KapacitorRulesPage extends Component {
|
|||
loading={loading}
|
||||
onDelete={this.handleDeleteRule}
|
||||
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 KapacitorRulesPage from './containers/KapacitorRulesPage'
|
||||
import KapacitorTasksPage from './containers/KapacitorTasksPage'
|
||||
import TickscriptPage from './containers/TickscriptPage'
|
||||
export {
|
||||
KapacitorPage,
|
||||
KapacitorRulePage,
|
||||
KapacitorRulesPage,
|
||||
KapacitorTasksPage,
|
||||
TickscriptPage,
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export function deleteSource(source) {
|
|||
export function pingKapacitor(kapacitor) {
|
||||
return AJAX({
|
||||
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'
|
||||
|
||||
const labelText = ({localSelectedItems, isOpen, label}) => {
|
||||
if (localSelectedItems.length) {
|
||||
return localSelectedItems.map(s => s.name).join(', ')
|
||||
}
|
||||
|
||||
if (label) {
|
||||
return label
|
||||
} else if (localSelectedItems.length) {
|
||||
return localSelectedItems.map(s => s).join(', ')
|
||||
}
|
||||
|
||||
// TODO: be smarter about the text displayed here
|
||||
if (isOpen) {
|
||||
return '0 Selected'
|
||||
}
|
||||
|
||||
return 'None'
|
||||
}
|
||||
|
||||
|
@ -27,42 +30,52 @@ class MultiSelectDropdown extends Component {
|
|||
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
localSelectedItems: this.props.selectedItems,
|
||||
localSelectedItems: props.selectedItems,
|
||||
}
|
||||
|
||||
this.onSelect = ::this.onSelect
|
||||
this.onApplyFunctions = ::this.onApplyFunctions
|
||||
}
|
||||
|
||||
handleClickOutside() {
|
||||
this.setState({isOpen: false})
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!_.isEqual(this.props.selectedItems, nextProps.selectedItems)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.setState({localSelectedItems: nextProps.selectedItems})
|
||||
}
|
||||
|
||||
toggleMenu = e => {
|
||||
e.stopPropagation()
|
||||
this.setState({isOpen: !this.state.isOpen})
|
||||
}
|
||||
|
||||
onSelect(item, e) {
|
||||
onSelect = (item, e) => {
|
||||
e.stopPropagation()
|
||||
|
||||
const {onApply, isApplyShown} = this.props
|
||||
const {localSelectedItems} = this.state
|
||||
|
||||
let nextItems
|
||||
if (this.isSelected(item)) {
|
||||
nextItems = localSelectedItems.filter(i => i !== item)
|
||||
nextItems = localSelectedItems.filter(i => i.name !== item.name)
|
||||
} else {
|
||||
nextItems = [...localSelectedItems, item]
|
||||
}
|
||||
|
||||
if (!isApplyShown) {
|
||||
onApply(nextItems)
|
||||
}
|
||||
|
||||
this.setState({localSelectedItems: nextItems})
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
this.setState({isOpen: false})
|
||||
|
@ -91,18 +104,18 @@ class MultiSelectDropdown extends Component {
|
|||
}
|
||||
|
||||
renderMenu() {
|
||||
const {items} = this.props
|
||||
|
||||
return (
|
||||
<ul className="dropdown-menu">
|
||||
<li className="multi-select--apply">
|
||||
<button
|
||||
className="btn btn-xs btn-info"
|
||||
onClick={this.onApplyFunctions}
|
||||
>
|
||||
const {items, isApplyShown} = this.props
|
||||
const applyButton = isApplyShown
|
||||
? <li className="multi-select--apply">
|
||||
<button className="btn btn-xs btn-info" onClick={this.handleApply}>
|
||||
Apply
|
||||
</button>
|
||||
</li>
|
||||
: null
|
||||
|
||||
return (
|
||||
<ul className="dropdown-menu">
|
||||
{applyButton}
|
||||
<FancyScrollbar
|
||||
autoHide={false}
|
||||
autoHeight={true}
|
||||
|
@ -119,7 +132,7 @@ class MultiSelectDropdown extends Component {
|
|||
>
|
||||
<div className="multi-select--checkbox" />
|
||||
<span>
|
||||
{listItem}
|
||||
{listItem.name}
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
|
@ -130,22 +143,34 @@ class MultiSelectDropdown extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {arrayOf, func, string} = PropTypes
|
||||
const {arrayOf, bool, func, shape, string} = PropTypes
|
||||
|
||||
MultiSelectDropdown.propTypes = {
|
||||
onApply: func.isRequired,
|
||||
items: arrayOf(string.isRequired).isRequired,
|
||||
selectedItems: arrayOf(string.isRequired).isRequired,
|
||||
items: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
})
|
||||
).isRequired,
|
||||
selectedItems: arrayOf(
|
||||
shape({
|
||||
name: string.isRequired,
|
||||
})
|
||||
),
|
||||
label: string,
|
||||
buttonSize: string,
|
||||
buttonColor: string,
|
||||
customClass: string,
|
||||
iconName: string,
|
||||
isApplyShown: bool,
|
||||
}
|
||||
|
||||
MultiSelectDropdown.defaultProps = {
|
||||
buttonSize: 'btn-sm',
|
||||
buttonColor: 'btn-default',
|
||||
customClass: 'dropdown-160',
|
||||
selectedItems: [],
|
||||
isApplyShown: true,
|
||||
}
|
||||
|
||||
export default OnClickOutside(MultiSelectDropdown)
|
||||
|
|
|
@ -102,11 +102,9 @@ const SideNav = React.createClass({
|
|||
link={`${sourcePrefix}/alerts`}
|
||||
>
|
||||
<NavHeader link={`${sourcePrefix}/alerts`} title="Alerting" />
|
||||
<NavListItem link={`${sourcePrefix}/alerts`}>
|
||||
Alert History
|
||||
</NavListItem>
|
||||
<NavListItem link={`${sourcePrefix}/alerts`}>History</NavListItem>
|
||||
<NavListItem link={`${sourcePrefix}/alert-rules`}>
|
||||
Alert Rules
|
||||
Create
|
||||
</NavListItem>
|
||||
</NavBlock>
|
||||
<NavBlock icon="crown2" link={`${sourcePrefix}/admin`}>
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
@import 'theme/bootstrap-theme';
|
||||
@import 'theme/reset';
|
||||
|
||||
// External
|
||||
// Vendor
|
||||
@import 'external/react-grid-layout';
|
||||
@import 'external/fixed-data-table-base';
|
||||
@import 'external/fixed-data-table-style';
|
||||
@import 'external/fixed-data-table';
|
||||
@import "external/codemirror";
|
||||
@import "../../node_modules/codemirror/theme/material.css";
|
||||
|
||||
// Layout
|
||||
@import 'layout/page';
|
||||
|
@ -27,6 +29,7 @@
|
|||
// Components
|
||||
@import 'components/ceo-display-options';
|
||||
@import 'components/confirm-buttons';
|
||||
@import 'components/code-mirror-theme';
|
||||
@import 'components/custom-time-range';
|
||||
@import 'components/dygraphs';
|
||||
@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-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:
|
||||
version "1.0.3"
|
||||
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"
|
||||
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:
|
||||
version "1.8.2"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.8.2.tgz#be868184d7c8631766d54e7078e2672d7c7e3339"
|
||||
|
@ -4402,6 +4416,10 @@ lodash.debounce@^3.1.1:
|
|||
dependencies:
|
||||
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:
|
||||
version "3.2.0"
|
||||
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"
|
||||
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:
|
||||
version "3.1.2"
|
||||
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:
|
||||
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:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f"
|
||||
|
@ -5814,6 +5842,13 @@ promise@^7.1.1:
|
|||
dependencies:
|
||||
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:
|
||||
version "15.5.8"
|
||||
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"
|
||||
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:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/react-custom-scrollbars/-/react-custom-scrollbars-4.1.1.tgz#cf08cd43b1297ab11e6fcc5c9a800e7b70b6f248"
|
||||
|
|
Loading…
Reference in New Issue