From c746343dc09fe5c37af452df2f0796861fa88644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 1 May 2024 10:02:33 +0200 Subject: [PATCH] Adding datapoints to triggers --- datapoint.go | 1 + main.go | 63 ++++++++++++++---- page.go | 1 - sql/00008.sql | 1 + static/css/datapoints.css | 4 +- static/css/main.css | 25 ++++++-- static/css/theme.css | 4 +- static/css/trigger_edit.css | 4 +- static/js/trigger_edit.mjs | 109 +++++++++++++++++++++++++++----- static/less/main.less | 28 +++++--- static/less/theme.less | 4 +- static/less/trigger_edit.less | 3 + trigger.go | 63 ++++++++++++------ views/pages/trigger_edit.gotmpl | 44 +++++++------ views/pages/triggers.gotmpl | 5 +- 15 files changed, 269 insertions(+), 90 deletions(-) create mode 100644 sql/00008.sql diff --git a/datapoint.go b/datapoint.go index 4d36578..9e925ad 100644 --- a/datapoint.go +++ b/datapoint.go @@ -36,6 +36,7 @@ type DatapointValue struct { ValueInt sql.NullInt64 `db:"value_int"` ValueString sql.NullString `db:"value_string"` ValueDateTime sql.NullTime `db:"value_datetime"` + TemplateValue any } func (dp DatapointValue) Value() any { // {{{ diff --git a/main.go b/main.go index 4d9101a..9e9e62d 100644 --- a/main.go +++ b/main.go @@ -103,13 +103,17 @@ func main() { // {{{ service.Register("/", false, false, staticHandler) service.Register("/problems", false, false, pageProblems) + service.Register("/datapoints", false, false, pageDatapoints) service.Register("/datapoint/edit/{id}", false, false, pageDatapointEdit) service.Register("/datapoint/update/{id}", false, false, pageDatapointUpdate) + service.Register("/triggers", false, false, pageTriggers) service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit) + service.Register("/trigger/edit/{id}/{sectionID}", false, false, pageTriggerEdit) service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate) service.Register("/trigger/run/{id}", false, false, pageTriggerRun) + service.Register("/configuration", false, false, pageConfiguration) service.Register("/entry/{datapoint}", false, false, entryDatapoint) @@ -181,13 +185,13 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { / if err != nil { err = we.Wrap(err).Log() logger.Error("entry", "error", err) - + } logger.Debug("entry", "datapoint", dpoint, "value", value, "trigger", trigger, "result", out) switch v := out.(type) { case bool: - // Trigger returning true - a problem occured + // Trigger returning true - a problem occurred if v { err = ProblemStart(trigger) if err != nil { @@ -272,6 +276,7 @@ func pageIndex(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ } page.Render(w) } // }}} + func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page := Page{ LAYOUT: "main", @@ -297,7 +302,8 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page.Render(w) return } // }}} -func pageDatapoints(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ + +func pageDatapoints(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ page := Page{ LAYOUT: "main", PAGE: "datapoints", @@ -308,7 +314,13 @@ func pageDatapoints(w http.ResponseWriter, _ *http.Request, _ *session.T) { // { httpError(w, we.Wrap(err).Log()) return } - logger.Info("FOO", "dps", datapoints) + + // The datapoint selector in trigger edit wants the raw data in JSON. + if r.URL.Query().Get("format") == "json" { + j, _ := json.Marshal(datapoints) + w.Write(j) + return + } page.Data = map[string]any{ "Datapoints": datapoints, @@ -371,6 +383,7 @@ func pageDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { w.Header().Add("Location", "/datapoints") w.WriteHeader(302) } // }}} + func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ areas, err := TriggersRetrieve() if err != nil { @@ -400,11 +413,26 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // return } + // Creating a new trigger uses the edit function. + // ID == 0 - create a new trigger. + // ID > 0 - edit existing trigger. var trigger Trigger - trigger, err = TriggerRetrieve(id) - if err != nil { - httpError(w, we.Wrap(err).Log()) - return + if id > 0 { + trigger, err = TriggerRetrieve(id) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + } else { + // A new trigger needs to know which section it belongs to. + sectionIDStr := r.PathValue("sectionID") + if sectionIDStr != "" { + trigger.SectionID, err = strconv.Atoi(sectionIDStr) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + } } datapoints := make(map[string]Datapoint) @@ -414,6 +442,7 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // httpError(w, we.Wrap(err).Log()) return } + dp.LastDatapointValue.TemplateValue = dp.LastDatapointValue.Value() datapoints[dpname] = dp } @@ -440,14 +469,23 @@ func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { / } var trigger Trigger - trigger, err = TriggerRetrieve(id) - if err != nil { - httpError(w, we.Wrap(err).Log()) - return + if id > 0 { + trigger, err = TriggerRetrieve(id) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + } else { + trigger.SectionID, err = strconv.Atoi(r.FormValue("sectionID")) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } } trigger.Name = r.FormValue("name") trigger.Expression = r.FormValue("expression") + trigger.Datapoints = r.Form["datapoints[]"] err = trigger.Update() if err != nil { httpError(w, we.Wrap(err).Log()) @@ -492,6 +530,7 @@ func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // { w.Header().Add("Content-Type", "application/json") w.Write(j) } // }}} + func pageConfiguration(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ areas, err := AreaRetrieve() if err != nil { diff --git a/page.go b/page.go index c6e22d4..2465645 100644 --- a/page.go +++ b/page.go @@ -52,7 +52,6 @@ func (p *Page) Render(w http.ResponseWriter) { "Data": p.Data, } - logger.Info("foo", "tmpl", tmpl) err = tmpl.Execute(w, data) if err != nil { httpError(w, we.Wrap(err).Log()) diff --git a/sql/00008.sql b/sql/00008.sql new file mode 100644 index 0000000..d7498df --- /dev/null +++ b/sql/00008.sql @@ -0,0 +1 @@ +ALTER TABLE public."trigger" ADD CONSTRAINT trigger_sectionname_unique UNIQUE (section_id,"name"); diff --git a/static/css/datapoints.css b/static/css/datapoints.css index dddb99b..ccc0c2a 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -37,7 +37,7 @@ h2 { font-size: 1.25em; } a { - color: #3f9da1; + color: #fabd2f; text-decoration: none; } a:hover { @@ -70,7 +70,7 @@ button { background: #202020; color: #d5c4a1; padding: 8px 32px; - border: 1px solid #3a3a3a; + border: 1px solid #535353; font-size: 1em; height: 3em; } diff --git a/static/css/main.css b/static/css/main.css index f5b869e..b29b9bb 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -37,7 +37,7 @@ h2 { font-size: 1.25em; } a { - color: #3f9da1; + color: #fabd2f; text-decoration: none; } a:hover { @@ -70,7 +70,7 @@ button { background: #202020; color: #d5c4a1; padding: 8px 32px; - border: 1px solid #3a3a3a; + border: 1px solid #535353; font-size: 1em; height: 3em; } @@ -137,12 +137,17 @@ button:focus { #areas .area .section { margin: 8px 16px; } -#areas .area .section > .name { +#areas .area .section .create { + display: grid; + grid-template-columns: min-content min-content; + grid-gap: 8px; + white-space: nowrap; +} +#areas .area .section .create .new { font-weight: 500; } -#areas .area .section .triggers a { - color: inherit; - text-decoration: none; +#areas .area .section > .name { + font-weight: 500; } #areas .area .section .triggers .trigger { display: grid; @@ -155,5 +160,11 @@ button:focus { height: 16px; } #areas .area .section .triggers .trigger .label { - color: #3f9da1; + color: inherit; +} +dialog { + background: #202020; + border: 1px solid #606060; + color: #d5c4a1; + box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.25); } diff --git a/static/css/theme.css b/static/css/theme.css index 6191e90..1c0dada 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -37,7 +37,7 @@ h2 { font-size: 1.25em; } a { - color: #3f9da1; + color: #fabd2f; text-decoration: none; } a:hover { @@ -70,7 +70,7 @@ button { background: #202020; color: #d5c4a1; padding: 8px 32px; - border: 1px solid #3a3a3a; + border: 1px solid #535353; font-size: 1em; height: 3em; } diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index 41e14b4..e5676af 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -37,7 +37,7 @@ h2 { font-size: 1.25em; } a { - color: #3f9da1; + color: #fabd2f; text-decoration: none; } a:hover { @@ -70,7 +70,7 @@ button { background: #202020; color: #d5c4a1; padding: 8px 32px; - border: 1px solid #3a3a3a; + border: 1px solid #535353; font-size: 1em; height: 3em; } diff --git a/static/js/trigger_edit.mjs b/static/js/trigger_edit.mjs index 3d18d82..e534562 100644 --- a/static/js/trigger_edit.mjs +++ b/static/js/trigger_edit.mjs @@ -1,17 +1,35 @@ export class UI { - constructor() { + constructor() {//{{{ document.getElementById('button-run'). - addEventListener('click', evt=>evt.preventDefault()) + addEventListener('click', evt => evt.preventDefault()) - document.addEventListener('keydown', evt=>this.keyHandler(evt)) - } - setTrigger(t) { + document.addEventListener('keydown', evt => this.keyHandler(evt)) + + document.querySelector('input[name="name"]').focus() + + this.datapoints = [] + }//}}} + render() {//{{{ + document.querySelectorAll('.datapoints .datapoint').forEach(el => el.remove()); + + const datapoints = document.querySelector('.datapoints') + + let html = Object.keys(this.trigger.datapoints).sort().map(dpName => { + const dp = this.trigger.datapoints[dpName] + return ` +
${dp.Name}
+
${dp.LastDatapointValue.TemplateValue}
+ ` + }).join('') + datapoints.innerHTML += html + }//}}} + setTrigger(t) {//{{{ this.trigger = t - } - run() { + }//}}} + run() {//{{{ this.trigger.run() - } - keyHandler(evt) { + }//}}} + keyHandler(evt) {//{{{ if (!(evt.altKey && evt.shiftKey)) return @@ -21,21 +39,75 @@ export class UI { switch (evt.key) { case 'T': this.run() - break + break case 'S': - document.getElementById('form-trigger').submit() - break + this.update() + break } - } + }//}}} + addDatapoint() {//{{{ + const dlg = document.getElementById('dlg-datapoints') + const datalist = document.getElementById('list-datapoints') + dlg.showModal() + + fetch('/datapoints?format=json') + .then(data => data.json()) + .then(json => { + this.datapoints = json + + let html = '' + this.datapoints.forEach(dp => { + html += `