[editor] Add validation for template fields

closes #38

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2023-04-02 14:55:27 +02:00
parent 0dc648d02a
commit 4f12b5c206
Signed by: luzifer
GPG Key ID: D91C3E91E4CAD6F5
5 changed files with 100 additions and 6 deletions

View File

@ -172,6 +172,10 @@ func patchConfig(filename, authorName, authorEmail, summary string, patcher func
return errors.Wrap(err, "patching config")
}
if err = cfgFile.runLoadChecks(); err != nil {
return errors.Wrap(err, "checking config after patch")
}
return errors.Wrap(
writeConfigToYAML(filename, authorName, authorEmail, summary, cfgFile),
"replacing config",

View File

@ -13,6 +13,7 @@ import (
var frontendReloadHooks = newHooker()
//nolint:funlen // Just contains a collection of objects
func registerEditorGlobalMethods() {
for _, rd := range []plugins.HTTPRouteRegistrationArgs{
{
@ -100,6 +101,23 @@ func registerEditorGlobalMethods() {
},
ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
},
{
Description: "Validate a template expression against the built in template function library",
HandlerFunc: configEditorGlobalValidateTemplate,
Method: http.MethodPut,
Module: "config-editor",
Name: "Validate template expression",
Path: "/validate-template",
QueryParams: []plugins.HTTPRouteParamDocumentation{
{
Description: "The template expression to test",
Name: "template",
Required: true,
Type: "string",
},
},
ResponseType: plugins.HTTPRouteResponseTypeTextPlain,
},
} {
if err := registerRoute(rd); err != nil {
log.WithError(err).Fatal("Unable to register config editor route")
@ -215,3 +233,12 @@ func configEditorGlobalValidateRegex(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
func configEditorGlobalValidateTemplate(w http.ResponseWriter, r *http.Request) {
if err := validateTemplate(r.FormValue("template")); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusNoContent)
}

View File

@ -124,6 +124,7 @@
id="formAutoMessageMessage"
v-model="models.autoMessage.message"
:state="models.autoMessage.message ? models.autoMessage.message.length <= validateAutoMessageMessageLength : false"
@valid-template="valid => updateTemplateValid('autoMessage.message', valid)"
/>
<div slot="description">
<font-awesome-icon
@ -227,6 +228,7 @@
<template-editor
id="formAutoMessageDisableOnTemplate"
v-model="models.autoMessage.disable_on_template"
@valid-template="valid => updateTemplateValid('autoMessage.disable_on_template', valid)"
/>
</b-form-group>
</b-col>
@ -284,6 +286,10 @@ export default {
return false
}
if (Object.entries(this.templateValid).filter(e => !e[1]).length > 0) {
return false
}
return true
},
@ -344,6 +350,7 @@ export default {
},
showAutoMessageEditModal: false,
templateValid: {},
}
},
@ -376,6 +383,7 @@ export default {
...msg,
sendMode: msg.cron ? 'cron' : 'lines',
})
this.templateValid = {}
this.showAutoMessageEditModal = true
},
@ -392,6 +400,7 @@ export default {
newAutoMessage() {
Vue.set(this.models, 'autoMessage', {})
this.templateValid = {}
this.showAutoMessageEditModal = true
},
@ -422,6 +431,10 @@ export default {
})
.catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
},
updateTemplateValid(id, valid) {
Vue.set(this.templateValid, id, valid)
},
},
mounted() {

View File

@ -398,6 +398,7 @@
<template-editor
id="formRuleDisableOnTemplate"
v-model="models.rule.disable_on_template"
@valid-template="valid => updateTemplateValid('rule.disable_on_template', valid)"
/>
</b-form-group>
</b-tab>
@ -603,6 +604,7 @@
:id="`${models.rule.uuid}-action-${idx}-${field.key}`"
v-model="models.rule.actions[idx].attributes[field.key]"
:state="validateActionArgument(idx, field.key)"
@valid-template="valid => updateTemplateValid(`${models.rule.uuid}-action-${idx}-${field.key}`, valid)"
/>
</b-form-group>
@ -781,6 +783,7 @@ export default {
showRuleEditModal: false,
showRuleSubscribeModal: false,
templateValid: {},
validateReason: null,
}
},
@ -850,6 +853,7 @@ export default {
cooldown: this.fixDurationRepresentationToString(msg.cooldown),
user_cooldown: this.fixDurationRepresentationToString(msg.user_cooldown),
})
this.templateValid = {}
this.showRuleEditModal = true
this.validateMatcherRegex()
},
@ -976,6 +980,7 @@ export default {
newRule() {
Vue.set(this.models, 'rule', { match_message__validation: true })
this.templateValid = {}
this.showRuleEditModal = true
},
@ -1094,6 +1099,10 @@ export default {
.catch(err => this.$bus.$emit(constants.NOTIFY_FETCH_ERROR, err))
},
updateTemplateValid(id, valid) {
Vue.set(this.templateValid, id, valid)
},
validateActionArgument(idx, key) {
const action = this.models.rule.actions[idx]
const def = this.getActionDefinitionByType(action.type)
@ -1189,6 +1198,11 @@ export default {
},
validateRule() {
if (Object.entries(this.templateValid).filter(e => !e[1]).length > 0) {
this.validateReason = 'templateValid'
return false
}
if (!this.models.rule.match_message__validation) {
this.validateReason = 'rule.match_message__validation'
return false

View File

@ -1,11 +1,20 @@
<template>
<div>
<div :class="wrapClasses">
<div ref="editor" />
</div>
<div
v-if="!isValid && validationError"
class="d-block invalid-feedback"
>
{{ validationError }}
</div>
</div>
</template>
<script>
import * as constants from './const.js'
import axios from 'axios'
import { CodeJar } from 'codejar/codejar.js'
import Prism from 'prismjs'
import { withLineNumbers } from 'codejar/linenumbers.js'
@ -38,8 +47,8 @@ export default {
wrapClasses() {
return {
'form-control': true,
'is-invalid': this.state === false,
'is-valid': this.state === true,
'is-invalid': this.state === false || !this.isValid,
'is-valid': this.state === true && this.isValid,
'template-editor': true,
}
},
@ -48,7 +57,9 @@ export default {
data() {
return {
emittedCode: '',
isValid: true,
jar: null,
validationError: '',
}
},
@ -57,6 +68,27 @@ export default {
const code = editor.textContent
editor.innerHTML = Prism.highlight(code, this.grammar, 'template')
},
validateTemplate(template) {
if (template === '') {
this.isValid = true
this.validationError = ''
this.$emit('valid-template', true)
return
}
return axios.put(`config-editor/validate-template?template=${encodeURIComponent(template)}`)
.then(() => {
this.isValid = true
this.validationError = ''
this.$emit('valid-template', true)
})
.catch(resp => {
this.isValid = false
this.validationError = resp.response.data.split(':1:')[1]
this.$emit('valid-template', false)
})
},
},
mounted() {
@ -65,6 +97,7 @@ export default {
tab: ' '.repeat(2),
})
this.jar.onUpdate(code => {
this.validateTemplate(code)
this.emittedCode = code
this.$emit('input', code)
})
@ -101,8 +134,6 @@ export default {
<style>
.template-editor {
background-color: #fff;
border-radius: 0.25rem;
color: #444;
font-family: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;
font-size: 87.5%;
@ -110,6 +141,11 @@ export default {
padding: 0;
}
.template-editor .codejar-wrap {
background-color: #fff;
border-radius: 0.25rem;
}
.template-editor .codejar-linenumbers {
padding-right: 0.5em;
text-align: right;