Compare commits
7 commits
Author | SHA1 | Date | |
---|---|---|---|
|
8fa8bb4c3a | ||
|
7cf2b60803 | ||
|
db41d360dc | ||
|
6685f8bc46 | ||
|
96f7b50e4e | ||
|
8ef6a2bbfa | ||
|
d1599fe2b9 |
6 changed files with 155 additions and 45 deletions
|
@ -242,11 +242,11 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{
|
|||
var query string
|
||||
var param any
|
||||
if id > 0 {
|
||||
query = `SELECT *, true AS found FROM datapoint WHERE id = $1`
|
||||
query = `SELECT id, "group", name, "datatype", comment, last_value, nodata_problem_seconds, nodata_is_problem, true AS found FROM public.datapoint WHERE id = $1`
|
||||
param = id
|
||||
dp.ID = id
|
||||
} else {
|
||||
query = `SELECT *, true AS found FROM datapoint WHERE name = $1`
|
||||
query = `SELECT id, "group", name, "datatype", comment, last_value, nodata_problem_seconds, nodata_is_problem, true AS found FROM public.datapoint WHERE name = $1`
|
||||
param = name
|
||||
}
|
||||
|
||||
|
|
56
main.go
56
main.go
|
@ -29,7 +29,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
const VERSION = "v38"
|
||||
const VERSION = "v41"
|
||||
|
||||
var (
|
||||
logger *slog.Logger
|
||||
|
@ -663,6 +663,27 @@ func pageDatapointEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { /
|
|||
}
|
||||
}
|
||||
|
||||
/* Triggers using this datapoint is provided as a list to update
|
||||
* if changing the datapoint name. Parsing expr and automatically
|
||||
* changing it to renamed datapoints would be nice in the future. */
|
||||
var triggers []Trigger
|
||||
triggers, err = TriggersRetrieveByDatapoint(datapoint.Name)
|
||||
if err != nil {
|
||||
httpError(w, werr.Wrap(err).Log())
|
||||
return
|
||||
}
|
||||
slices.SortFunc(triggers, func(a, b Trigger) int {
|
||||
an := strings.ToUpper(a.Name)
|
||||
bn := strings.ToUpper(b.Name)
|
||||
if an < bn {
|
||||
return -1
|
||||
}
|
||||
if an > bn {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
page := Page{
|
||||
LAYOUT: "main",
|
||||
PAGE: "datapoint_edit",
|
||||
|
@ -674,6 +695,7 @@ func pageDatapointEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { /
|
|||
|
||||
page.Data = map[string]any{
|
||||
"Datapoint": datapoint,
|
||||
"Triggers": triggers,
|
||||
}
|
||||
page.Render(w, r)
|
||||
return
|
||||
|
@ -689,8 +711,15 @@ func actionDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T)
|
|||
var nodataSeconds int
|
||||
nodataSeconds, _ = strconv.Atoi(r.FormValue("nodata_seconds"))
|
||||
|
||||
// Datapoint needs to be retrieved from database for the name.
|
||||
// If name has changed, trigger expressions needs to be updated.
|
||||
var dp Datapoint
|
||||
dp.ID = id
|
||||
dp, err = DatapointRetrieve(id, "")
|
||||
if err != nil {
|
||||
httpError(w, werr.Wrap(err).WithData(id).Log())
|
||||
return
|
||||
}
|
||||
prevDatapointName := dp.Name
|
||||
dp.Group = r.FormValue("group")
|
||||
dp.Name = r.FormValue("name")
|
||||
dp.Datatype = DatapointType(r.FormValue("datatype"))
|
||||
|
@ -702,6 +731,29 @@ func actionDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T)
|
|||
return
|
||||
}
|
||||
|
||||
// Update the trigger expressions using this
|
||||
// datapoint name if changed.
|
||||
if prevDatapointName != dp.Name {
|
||||
var triggers []Trigger
|
||||
triggers, err = TriggersRetrieveByDatapoint(dp.Name)
|
||||
if err != nil {
|
||||
httpError(w, werr.Wrap(err).WithData(dp.Name))
|
||||
return
|
||||
}
|
||||
for _, trigger := range triggers {
|
||||
err = trigger.RenameDatapoint(prevDatapointName, dp.Name)
|
||||
if err != nil {
|
||||
httpError(w, werr.Wrap(err).WithData([]string{prevDatapointName, dp.Name}))
|
||||
return
|
||||
}
|
||||
err = trigger.Update()
|
||||
if err != nil {
|
||||
httpError(w, werr.Wrap(err).WithData([]string{prevDatapointName, dp.Name, trigger.Name}))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Add("Location", "/datapoints")
|
||||
w.WriteHeader(302)
|
||||
} // }}}
|
||||
|
|
22
sql/00027.sql
Normal file
22
sql/00027.sql
Normal file
|
@ -0,0 +1,22 @@
|
|||
/* Updating a datapoint name also updates the jsonb array entry */
|
||||
CREATE OR REPLACE FUNCTION update_triggers_datapoint_name()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS
|
||||
$$
|
||||
BEGIN
|
||||
UPDATE "trigger"
|
||||
SET
|
||||
datapoints = (datapoints - OLD.name) || jsonb_build_array(NEW.name)
|
||||
WHERE
|
||||
datapoints ? OLD.name;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE TRIGGER datapoint_renamed
|
||||
AFTER UPDATE
|
||||
ON public.datapoint
|
||||
FOR EACH ROW
|
||||
EXECUTE PROCEDURE update_triggers_datapoint_name();
|
|
@ -1,5 +1,6 @@
|
|||
export class UI {
|
||||
constructor() {
|
||||
constructor(datapointData) {
|
||||
this.datapoint = datapointData
|
||||
document.addEventListener('keydown', evt=>this.keyHandler(evt))
|
||||
document.querySelector('input[name="group"]').focus()
|
||||
}
|
||||
|
|
40
trigger.go
40
trigger.go
|
@ -4,6 +4,8 @@ import (
|
|||
// External
|
||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
||||
"github.com/expr-lang/expr"
|
||||
"github.com/expr-lang/expr/ast"
|
||||
"github.com/expr-lang/expr/parser"
|
||||
"github.com/lib/pq"
|
||||
|
||||
// Standard
|
||||
|
@ -22,6 +24,17 @@ type Trigger struct {
|
|||
DatapointValues map[string]any
|
||||
}
|
||||
|
||||
type ExprRenamePatcher struct {
|
||||
OldName string
|
||||
NewName string
|
||||
}
|
||||
|
||||
func (p ExprRenamePatcher) Visit(node *ast.Node) {
|
||||
if n, ok := (*node).(*ast.IdentifierNode); ok && n.Value == p.OldName {
|
||||
ast.Patch(node, &ast.IdentifierNode{Value: p.NewName})
|
||||
}
|
||||
}
|
||||
|
||||
func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{
|
||||
t.SectionID = sectionID
|
||||
t.Name = name
|
||||
|
@ -127,6 +140,14 @@ func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{
|
|||
err = json.Unmarshal(jsonData, &trigger)
|
||||
return
|
||||
} // }}}
|
||||
func TriggerDelete(id int) (err error) { // {{{
|
||||
_, err = service.Db.Conn.Exec(`DELETE FROM public.trigger WHERE id=$1`, id)
|
||||
if err != nil {
|
||||
return werr.Wrap(err).WithData(id)
|
||||
}
|
||||
return
|
||||
} // }}}
|
||||
|
||||
func (t *Trigger) Validate() (ok bool, err error) { // {{{
|
||||
if strings.TrimSpace(t.Name) == "" {
|
||||
err = fmt.Errorf("Name can't be empty")
|
||||
|
@ -212,14 +233,6 @@ func (t *Trigger) Update() (err error) { // {{{
|
|||
}
|
||||
return
|
||||
} // }}}
|
||||
func TriggerDelete(id int) (err error) { // {{{
|
||||
_, err = service.Db.Conn.Exec(`DELETE FROM public.trigger WHERE id=$1`, id)
|
||||
if err != nil {
|
||||
return werr.Wrap(err).WithData(id)
|
||||
}
|
||||
return
|
||||
} // }}}
|
||||
|
||||
func (t *Trigger) Run() (output any, err error) { // {{{
|
||||
datapoints := make(map[string]Datapoint)
|
||||
for _, dpname := range t.Datapoints {
|
||||
|
@ -248,3 +261,14 @@ func (t *Trigger) Run() (output any, err error) { // {{{
|
|||
}
|
||||
return
|
||||
} // }}}
|
||||
func (t *Trigger) RenameDatapoint(from, to string) error { // {{{
|
||||
tree, err := parser.Parse(t.Expression)
|
||||
if err != nil {
|
||||
return werr.Wrap(err).WithData(t.Expression)
|
||||
}
|
||||
|
||||
ast.Walk(&tree.Node, ExprRenamePatcher{from, to})
|
||||
t.Expression = tree.Node.String()
|
||||
|
||||
return nil
|
||||
} // }}}
|
||||
|
|
|
@ -3,48 +3,59 @@
|
|||
|
||||
<script type="module" defer>
|
||||
import {UI} from "/js/{{ .VERSION }}/datapoint_edit.mjs"
|
||||
window._ui = new UI()
|
||||
window._ui = new UI({{ .Data.Datapoint }})
|
||||
</script>
|
||||
|
||||
{{ block "page_label" . }}{{end}}
|
||||
|
||||
<form id="form-trigger" action="/datapoint/update/{{ .Data.Datapoint.ID }}" method="post">
|
||||
<div id="widgets" class="widgets">
|
||||
<div class="label">Group</div>
|
||||
<div><input type="text" name="group" value="{{ .Data.Datapoint.Group }}"></div>
|
||||
<div id="widgets" class="widgets">
|
||||
<div class="label">Group</div>
|
||||
<div><input type="text" name="group" value="{{ .Data.Datapoint.Group }}"></div>
|
||||
|
||||
<div class="label">Name</div>
|
||||
<div><input type="text" name="name" value="{{ .Data.Datapoint.Name }}"></div>
|
||||
|
||||
<div class="label">Datatype</div>
|
||||
<div>
|
||||
<select name="datatype">
|
||||
<option {{ if eq .Data.Datapoint.Datatype "INT" }}selected{{end}}>INT</option>
|
||||
<option {{ if eq .Data.Datapoint.Datatype "STRING" }}selected{{end}}>STRING</option>
|
||||
<option {{ if eq .Data.Datapoint.Datatype "DATETIME" }}selected{{end}}>DATETIME</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="label">Name</div>
|
||||
<div><input type="text" name="name" value="{{ .Data.Datapoint.Name }}"></div>
|
||||
|
||||
<div class="label">No data<br>problem time<br>(seconds)</div>
|
||||
<div>
|
||||
<input type="text" name="nodata_seconds" value="{{ .Data.Datapoint.NodataProblemSeconds }}">
|
||||
<div class="description">A problem is raised and notified if an entry isn't made within this time.</div>
|
||||
<div class="description">Set to 0 to disable.</div>
|
||||
</div>
|
||||
<div class="label">Datatype</div>
|
||||
<div>
|
||||
<select name="datatype">
|
||||
<option {{ if eq .Data.Datapoint.Datatype "INT" }}selected{{end}}>INT</option>
|
||||
<option {{ if eq .Data.Datapoint.Datatype "STRING" }}selected{{end}}>STRING</option>
|
||||
<option {{ if eq .Data.Datapoint.Datatype "DATETIME" }}selected{{end}}>DATETIME</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="label">Comment</div>
|
||||
<div>
|
||||
<textarea name="comment" rows=4>{{ .Data.Datapoint.Comment }}</textarea>
|
||||
</div>
|
||||
<div class="label">No data<br>problem time<br>(seconds)</div>
|
||||
<div>
|
||||
<input type="text" name="nodata_seconds" value="{{ .Data.Datapoint.NodataProblemSeconds }}">
|
||||
<div class="description">A problem is raised and notified if an entry isn't made within this time.</div>
|
||||
<div class="description">Set to 0 to disable.</div>
|
||||
</div>
|
||||
|
||||
<div class="label">Comment</div>
|
||||
<div>
|
||||
<textarea name="comment" rows=4>{{ .Data.Datapoint.Comment }}</textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<div></div>
|
||||
<div class="action">
|
||||
{{ if eq .Data.Datapoint.ID 0 }}
|
||||
<button id="button-update">Create</button>
|
||||
{{ else }}
|
||||
<button id="button-update">Update</button>
|
||||
{{ end }}
|
||||
<div></div>
|
||||
<div class="action">
|
||||
{{ if eq .Data.Datapoint.ID 0 }}
|
||||
<button id="button-update">Create</button>
|
||||
{{ else }}
|
||||
<button id="button-update">Update</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div></div>
|
||||
<div style="margin-top: 32px">
|
||||
<b>Used in the following triggers:</b>
|
||||
<ul>
|
||||
{{ range .Data.Triggers }}
|
||||
<li><a href="/trigger/edit/{{ .ID }}">{{ .Name }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue