Merge pull request #5805 from influxdata/5547/templated_task_readOnly
fix(kapacitor): make templated kapacitor task read-onlypull/5812/head
commit
b0458a3a95
|
@ -9,6 +9,7 @@
|
|||
1. [#5796](https://github.com/influxdata/chronograf/pull/5796): Avoid useless browser history change.
|
||||
1. [#5803](https://github.com/influxdata/chronograf/pull/5803): Repair time rendering in horizontal table.
|
||||
1. [#5804](https://github.com/influxdata/chronograf/pull/5804): Name tickscript after a `name` task variable, when defined.
|
||||
1. [#5805](https://github.com/influxdata/chronograf/pull/5805): Make templated tasks read-only.
|
||||
1. [#5806](https://github.com/influxdata/chronograf/pull/5806): Repair paginated retrival of flux tasks.
|
||||
|
||||
### Features
|
||||
|
|
|
@ -8,6 +8,8 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/influxdata/kapacitor/client/v1"
|
||||
)
|
||||
|
||||
// General errors.
|
||||
|
@ -274,24 +276,26 @@ type DBRP struct {
|
|||
|
||||
// AlertRule represents rules for building a tickscript alerting task
|
||||
type AlertRule struct {
|
||||
ID string `json:"id,omitempty"` // ID is the unique ID of the alert
|
||||
TICKScript TICKScript `json:"tickscript"` // TICKScript is the raw tickscript associated with this Alert
|
||||
Query *QueryConfig `json:"query"` // Query is the filter of data for the alert.
|
||||
Every string `json:"every"` // Every how often to check for the alerting criteria
|
||||
AlertNodes AlertNodes `json:"alertNodes"` // AlertNodes defines the destinations for the alert
|
||||
Message string `json:"message"` // Message included with alert
|
||||
Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added.
|
||||
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
||||
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
||||
Name string `json:"name"` // Name is the user-defined name for the alert
|
||||
Type string `json:"type"` // Represents the task type where stream is data streamed to kapacitor and batch is queried by kapacitor
|
||||
DBRPs []DBRP `json:"dbrps"` // List of database retention policy pairs the task is allowed to access
|
||||
Status string `json:"status"` // Represents if this rule is enabled or disabled in kapacitor
|
||||
Executing bool `json:"executing"` // Whether the task is currently executing
|
||||
Error string `json:"error"` // Any error encountered when kapacitor executes the task
|
||||
Created time.Time `json:"created"` // Date the task was first created
|
||||
Modified time.Time `json:"modified"` // Date the task was last modified
|
||||
LastEnabled time.Time `json:"last-enabled,omitempty"` // Date the task was last set to status enabled
|
||||
ID string `json:"id,omitempty"` // ID is the unique ID of the alert
|
||||
TICKScript TICKScript `json:"tickscript"` // TICKScript is the raw tickscript associated with this Alert
|
||||
Query *QueryConfig `json:"query"` // Query is the filter of data for the alert.
|
||||
Every string `json:"every"` // Every how often to check for the alerting criteria
|
||||
AlertNodes AlertNodes `json:"alertNodes"` // AlertNodes defines the destinations for the alert
|
||||
Message string `json:"message"` // Message included with alert
|
||||
Details string `json:"details"` // Details is generally used for the Email alert. If empty will not be added.
|
||||
Trigger string `json:"trigger"` // Trigger is a type that defines when to trigger the alert
|
||||
TriggerValues TriggerValues `json:"values"` // Defines the values that cause the alert to trigger
|
||||
Name string `json:"name"` // Name is the user-defined name for the alert
|
||||
Type string `json:"type"` // Represents the task type where stream is data streamed to kapacitor and batch is queried by kapacitor
|
||||
DBRPs []DBRP `json:"dbrps"` // List of database retention policy pairs the task is allowed to access
|
||||
Status string `json:"status"` // Represents if this rule is enabled or disabled in kapacitor
|
||||
Executing bool `json:"executing"` // Whether the task is currently executing
|
||||
Error string `json:"error"` // Any error encountered when kapacitor executes the task
|
||||
Created time.Time `json:"created"` // Date the task was first created
|
||||
Modified time.Time `json:"modified"` // Date the task was last modified
|
||||
LastEnabled time.Time `json:"last-enabled,omitempty"` // Date the task was last set to status enabled
|
||||
Vars map[string]client.Var `json:"vars,omitempty"` // task variables when defined
|
||||
TemplateID string `json:"template-id,omitempty"` // task template id when created from template
|
||||
}
|
||||
|
||||
// TICKScript task to be used by kapacitor
|
||||
|
|
|
@ -80,18 +80,22 @@ func NewTask(task *client.Task) *Task {
|
|||
}
|
||||
|
||||
script := chronograf.TICKScript(task.TICKscript)
|
||||
rule, err := Reverse(script)
|
||||
if err != nil {
|
||||
// try to parse Name from a line such as: `var name = 'Rule Name'
|
||||
name := task.ID
|
||||
if matches := reTaskName.FindStringSubmatch(task.TICKscript); matches != nil {
|
||||
name = matches[1]
|
||||
}
|
||||
rule = chronograf.AlertRule{
|
||||
Name: name,
|
||||
Query: nil,
|
||||
var rule chronograf.AlertRule = chronograf.AlertRule{Query: nil}
|
||||
if task.TemplateID == "" {
|
||||
// try to parse chronograf rule, tasks created from template cannot be chronograf rules
|
||||
if parsedRule, err := Reverse(script); err == nil {
|
||||
rule = parsedRule
|
||||
}
|
||||
}
|
||||
if rule.Name == "" {
|
||||
// try to parse Name from a line such as: `var name = 'Rule Name'
|
||||
if matches := reTaskName.FindStringSubmatch(task.TICKscript); matches != nil {
|
||||
rule.Name = matches[1]
|
||||
} else {
|
||||
rule.Name = task.ID
|
||||
}
|
||||
}
|
||||
|
||||
// #5403 override name when defined in a variable
|
||||
if nameVar, exists := task.Vars["name"]; exists {
|
||||
if val, isString := nameVar.Value.(string); isString && val != "" {
|
||||
|
@ -99,6 +103,8 @@ func NewTask(task *client.Task) *Task {
|
|||
}
|
||||
}
|
||||
|
||||
rule.Vars = task.Vars
|
||||
rule.TemplateID = task.TemplateID
|
||||
rule.ID = task.ID
|
||||
rule.TICKScript = script
|
||||
rule.Type = task.Type.String()
|
||||
|
@ -320,6 +326,7 @@ func (c *Client) Get(ctx context.Context, id string) (*Task, error) {
|
|||
if err != nil {
|
||||
return nil, chronograf.ErrAlertNotFound
|
||||
}
|
||||
fmt.Println("!!!", task.ID, task.TemplateID)
|
||||
|
||||
return NewTask(&task), nil
|
||||
}
|
||||
|
|
|
@ -4631,6 +4631,30 @@
|
|||
"description": "Date the task was last set to status enabled",
|
||||
"readOnly": true
|
||||
},
|
||||
"template-id": {
|
||||
"type": "string",
|
||||
"description": "Template ID when created from template",
|
||||
"readOnly": true
|
||||
},
|
||||
"vars": {
|
||||
"type": "object",
|
||||
"description": "task external variables",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "Must be either a string, bool, numeric or a list of Var objects."
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"required": ["self", "kapacitor"],
|
||||
|
|
|
@ -38,13 +38,18 @@ export function fetchRule(source, ruleID) {
|
|||
return dispatch => {
|
||||
getActiveKapacitor(source).then(kapacitor => {
|
||||
getRuleAJAX(kapacitor, ruleID).then(({data: rule}) => {
|
||||
if (rule.query) {
|
||||
rule = Object.assign(rule, {queryID: rule.query.id})
|
||||
}
|
||||
dispatch({
|
||||
type: 'LOAD_RULE',
|
||||
payload: {
|
||||
rule: Object.assign(rule, {queryID: rule.query.id}),
|
||||
rule,
|
||||
},
|
||||
})
|
||||
dispatch(loadQuery(rule.query))
|
||||
if (rule.query) {
|
||||
dispatch(loadQuery(rule.query))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -70,11 +70,13 @@ class Tickscript extends PureComponent<Props> {
|
|||
/>
|
||||
<TickscriptEditor
|
||||
script={task.tickscript}
|
||||
readOnly={!!task.templateID}
|
||||
onChangeScript={onChangeScript}
|
||||
/>
|
||||
<TickscriptEditorConsole
|
||||
consoleMessage={consoleMessage}
|
||||
unsavedChanges={unsavedChanges}
|
||||
task={task}
|
||||
/>
|
||||
</div>
|
||||
{this.logsTable}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
interface Props {
|
||||
onChangeScript: (tickscript: string) => void
|
||||
script: string
|
||||
readOnly?: boolean
|
||||
}
|
||||
|
||||
const NOOP = () => {}
|
||||
|
@ -19,13 +20,13 @@ class TickscriptEditor extends Component<Props> {
|
|||
}
|
||||
|
||||
public render() {
|
||||
const {script} = this.props
|
||||
const {script, readOnly} = this.props
|
||||
|
||||
const options = {
|
||||
lineNumbers: true,
|
||||
theme: 'tickscript',
|
||||
tabIndex: 1,
|
||||
readonly: false,
|
||||
readOnly: !!readOnly,
|
||||
mode: 'tickscript',
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import React, {FunctionComponent} from 'react'
|
||||
import {Task} from 'src/types'
|
||||
|
||||
interface Props {
|
||||
consoleMessage: string
|
||||
unsavedChanges: boolean
|
||||
task: Task
|
||||
}
|
||||
|
||||
const TickscriptEditorConsole: FunctionComponent<Props> = ({
|
||||
consoleMessage,
|
||||
unsavedChanges,
|
||||
task,
|
||||
}) => {
|
||||
let consoleOutput = 'TICKscript is valid'
|
||||
let consoleClass = 'tickscript-console--valid'
|
||||
|
@ -22,7 +25,24 @@ const TickscriptEditorConsole: FunctionComponent<Props> = ({
|
|||
|
||||
return (
|
||||
<div className="tickscript-console">
|
||||
<p className={consoleClass}>{consoleOutput}</p>
|
||||
{task.templateID ? (
|
||||
<p className={consoleClass}>
|
||||
TickScript is READ-ONLY, it was created from template{' '}
|
||||
{task.templateID}.
|
||||
</p>
|
||||
) : (
|
||||
<p className={consoleClass}>{consoleOutput}</p>
|
||||
)}
|
||||
{task.vars && Object.keys(task.vars).length ? (
|
||||
<p>
|
||||
Variables:{' '}
|
||||
{Object.keys(task.vars).map(name => (
|
||||
<span key={name}>
|
||||
{name}:{task.vars[name].type}={String(task.vars[name].value)}{' '}
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
) : undefined}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -27,14 +27,16 @@ class TickscriptEditorControls extends Component<Props> {
|
|||
return (
|
||||
<div className="tickscript-controls">
|
||||
{this.tickscriptID}
|
||||
<div className="tickscript-controls--right">
|
||||
<TickscriptType type={task.type} onChangeType={onChangeType} />
|
||||
<MultiSelectDBDropdown
|
||||
selectedItems={this.addName(task.dbrps)}
|
||||
onApply={onSelectDbrps}
|
||||
rightAligned={true}
|
||||
/>
|
||||
</div>
|
||||
{!task.name || task.templateID ? undefined : (
|
||||
<div className="tickscript-controls--right">
|
||||
<TickscriptType type={task.type} onChangeType={onChangeType} />
|
||||
<MultiSelectDBDropdown
|
||||
selectedItems={this.addName(task.dbrps)}
|
||||
onApply={onSelectDbrps}
|
||||
rightAligned={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -39,12 +39,14 @@ class TickscriptHeader extends Component<Props> {
|
|||
areLogsVisible={areLogsVisible}
|
||||
onToggleLogsVisibility={onToggleLogsVisibility}
|
||||
/>
|
||||
<TickscriptSave
|
||||
task={task}
|
||||
onSave={onSave}
|
||||
unsavedChanges={unsavedChanges}
|
||||
isNewTickscript={isNewTickscript}
|
||||
/>
|
||||
{task.templateID ? undefined : (
|
||||
<TickscriptSave
|
||||
task={task}
|
||||
onSave={onSave}
|
||||
unsavedChanges={unsavedChanges}
|
||||
isNewTickscript={isNewTickscript}
|
||||
/>
|
||||
)}
|
||||
{this.saveButton}
|
||||
</Page.Header.Right>
|
||||
</Page.Header>
|
||||
|
|
|
@ -5,6 +5,7 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
|
|||
export interface Task {
|
||||
dbrps: DBRP[]
|
||||
id: string
|
||||
templateID?: string
|
||||
}
|
||||
|
||||
interface SaveProps {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, {Component} from 'react'
|
||||
import _ from 'lodash'
|
||||
import {InjectedRouter} from 'react-router'
|
||||
import {InjectedRouter, Link} from 'react-router'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import PageSpinner from 'src/shared/components/PageSpinner'
|
||||
|
@ -36,6 +36,7 @@ import {
|
|||
KapacitorQueryConfigActions,
|
||||
KapacitorRuleActions,
|
||||
} from 'src/types/actions'
|
||||
import {Page} from 'src/reusable_ui'
|
||||
|
||||
interface Params {
|
||||
ruleID: string
|
||||
|
@ -111,6 +112,24 @@ class KapacitorRulePage extends Component<Props, State> {
|
|||
const rule = this.rule
|
||||
const query = rule && queryConfigs[rule.queryID]
|
||||
|
||||
if (rule && rule['template-id'] && kapacitor) {
|
||||
return (
|
||||
<Page>
|
||||
<Page.Contents>
|
||||
<div>
|
||||
This rule was created from a template. It cannot be edited in
|
||||
chronograf, see its{' '}
|
||||
<Link
|
||||
to={`sources/${source.id}/kapacitors/${kapacitor.id}/tickscripts/${rule.id}`}
|
||||
>
|
||||
TICKScript
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
if (!query) {
|
||||
return <PageSpinner />
|
||||
}
|
||||
|
|
|
@ -140,11 +140,29 @@ export class TickscriptPage extends PureComponent<Props, State> {
|
|||
|
||||
if (this.isEditing) {
|
||||
await kapacitorActions.getRule(kapacitor, ruleID)
|
||||
const {id, name, tickscript, status, dbrps, type} = this.props.rules.find(
|
||||
r => r.id === ruleID
|
||||
)
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
tickscript,
|
||||
status,
|
||||
dbrps,
|
||||
type,
|
||||
'template-id': templateID,
|
||||
vars,
|
||||
} = this.props.rules.find(r => r.id === ruleID)
|
||||
|
||||
this.setState({task: {tickscript, dbrps, type, status, name, id}})
|
||||
this.setState({
|
||||
task: {
|
||||
tickscript,
|
||||
dbrps,
|
||||
type,
|
||||
status,
|
||||
name,
|
||||
id,
|
||||
templateID,
|
||||
vars,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
this.fetchChunkedLogs(kapacitor, ruleID)
|
||||
|
|
|
@ -44,7 +44,7 @@ $tickscript-controls-height: 60px;
|
|||
> * {margin-left: 8px;}
|
||||
}
|
||||
.tickscript-console {
|
||||
align-items: flex-start;
|
||||
display: block;
|
||||
height: $tickscript-controls-height * 2.25;
|
||||
border-top: 2px solid $g3-castle;
|
||||
background-color: $g0-obsidian;
|
||||
|
|
|
@ -55,6 +55,8 @@ export interface AlertRule {
|
|||
modified: string
|
||||
queryID?: string
|
||||
'last-enabled'?: string
|
||||
'template-id'?: string
|
||||
vars?: Record<string, {type: string; value: unknown}>
|
||||
}
|
||||
|
||||
export interface Task {
|
||||
|
@ -64,6 +66,8 @@ export interface Task {
|
|||
tickscript: string
|
||||
dbrps: DBRP[]
|
||||
type: string
|
||||
templateID?: string
|
||||
vars?: Record<string, {type: string; value: unknown}>
|
||||
}
|
||||
|
||||
export type TaskStatusType = 'active' | 'inactive'
|
||||
|
|
Loading…
Reference in New Issue