From b83adad7c83f922a0f0cfe7c6eda229ac7f52b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 2 Jun 2024 09:17:50 +0200 Subject: [PATCH 1/4] Added area deletion --- area.go | 69 ++++++++++++++-- main.go | 38 +++++++++ section.go | 23 +++++- static/css/configuration.css | 130 +++++++++++++++++++++++++++++++ static/css/main.css | 4 - static/images/delete_white.svg | 67 ++++++++++++++++ static/less/configuration.less | 38 +++++++++ static/less/main.less | 5 -- views/pages/configuration.gotmpl | 23 +++++- views/pages/triggers.gotmpl | 3 + 10 files changed, 379 insertions(+), 21 deletions(-) create mode 100644 static/css/configuration.css create mode 100644 static/images/delete_white.svg create mode 100644 static/less/configuration.less diff --git a/area.go b/area.go index 8d95156..18e2b38 100644 --- a/area.go +++ b/area.go @@ -2,9 +2,10 @@ package main import ( // External - re "git.gibonuddevalla.se/go/wrappederror" + werr "git.gibonuddevalla.se/go/wrappederror" // Standard + "database/sql" "encoding/json" "sort" ) @@ -41,7 +42,7 @@ func AreaRetrieve() (areas []Area, err error) { // {{{ var jsonData []byte err = row.Scan(&jsonData) if err != nil { - err = re.Wrap(err) + err = werr.Wrap(err) return } @@ -51,20 +52,74 @@ func AreaRetrieve() (areas []Area, err error) { // {{{ err = json.Unmarshal(jsonData, &areas) if err != nil { - err = re.Wrap(err) + err = werr.Wrap(err) return } return } // }}} -func AreaCreate(name string) (err error) {// {{{ +func AreaCreate(name string) (err error) { // {{{ _, err = service.Db.Conn.Exec(`INSERT INTO area(name) VALUES($1)`, name) return -}// }}} -func AreaRename(id int, name string) (err error) {// {{{ +} // }}} +func AreaRename(id int, name string) (err error) { // {{{ _, err = service.Db.Conn.Exec(`UPDATE area SET name=$2 WHERE id=$1`, id, name) return -}// }}} +} // }}} +func AreaDelete(id int) (err error) { // {{{ + var trx *sql.Tx + trx, err = service.Db.Conn.Begin() + if err != nil { + err = werr.Wrap(err).WithData(id) + } + + _, err = trx.Exec(` + DELETE + FROM trigger t + USING section s + WHERE + t.section_id = s.id AND + s.area_id = $1 + `, + id, + ) + if err != nil { + err2 := trx.Rollback() + if err2 != nil { + return werr.Wrap(err2).WithData(err) + } + return werr.Wrap(err).WithData(id) + } + + _, err = trx.Exec(`DELETE FROM public.section WHERE area_id = $1`, id) + if err != nil { + err2 := trx.Rollback() + if err2 != nil { + return werr.Wrap(err2).WithData(err) + } + return werr.Wrap(err).WithData(id) + } + + _, err = trx.Exec(`DELETE FROM public.area WHERE id = $1`, id) + if err != nil { + err2 := trx.Rollback() + if err2 != nil { + return werr.Wrap(err2).WithData(err) + } + return werr.Wrap(err).WithData(id) + } + + err = trx.Commit() + if err != nil { + err2 := trx.Rollback() + if err2 != nil { + return werr.Wrap(err2).WithData(err) + } + return werr.Wrap(err).WithData(id) + } + + return nil +} // }}} func (a Area) SortedSections() []Section { // {{{ sort.SliceStable(a.Sections, func(i, j int) bool { diff --git a/main.go b/main.go index 9496441..69f80c8 100644 --- a/main.go +++ b/main.go @@ -122,9 +122,11 @@ func main() { // {{{ service.Register("/area/new/{name}", false, false, areaNew) service.Register("/area/rename/{id}/{name}", false, false, areaRename) + service.Register("/area/delete/{id}", false, false, areaDelete) service.Register("/section/new/{areaID}/{name}", false, false, sectionNew) service.Register("/section/rename/{id}/{name}", false, false, sectionRename) + service.Register("/section/delete/{id}", false, false, sectionDelete) service.Register("/problems", false, false, pageProblems) service.Register("/problem/acknowledge/{id}", false, false, pageProblemAcknowledge) @@ -399,6 +401,24 @@ func areaRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ w.WriteHeader(302) return } // }}} +func areaDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ + idStr := r.PathValue("id") + id, err := strconv.Atoi(idStr) + if err != nil { + httpError(w, werr.Wrap(err).WithData(idStr).Log()) + return + } + + err = AreaDelete(id) + if err != nil { + httpError(w, werr.Wrap(err).WithData(id).Log()) + return + } + + w.Header().Add("Location", "/configuration") + w.WriteHeader(302) + return +} // }}} func sectionNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("areaID") @@ -438,6 +458,24 @@ func sectionRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{ w.WriteHeader(302) return } // }}} +func sectionDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ + idStr := r.PathValue("id") + id, err := strconv.Atoi(idStr) + if err != nil { + httpError(w, werr.Wrap(err).WithData(idStr).Log()) + return + } + + err = SectionDelete(id) + if err != nil { + httpError(w, werr.Wrap(err).WithData(id).Log()) + return + } + + w.Header().Add("Location", "/configuration") + w.WriteHeader(302) + return +} // }}} func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page := Page{ diff --git a/section.go b/section.go index b86f5c0..82177d9 100644 --- a/section.go +++ b/section.go @@ -1,6 +1,9 @@ package main import ( + // External + werr "git.gibonuddevalla.se/go/wrappederror" + // Standard "sort" ) @@ -22,11 +25,23 @@ func (s *Section) SortedTriggers() []Trigger { return s.Triggers } -func SectionCreate(areaID int, name string) (err error) {// {{{ +func SectionCreate(areaID int, name string) (err error) { // {{{ _, err = service.Db.Conn.Exec(`INSERT INTO section(area_id, name) VALUES($1, $2)`, areaID, name) return -}// }}} -func SectionRename(id int, name string) (err error) {// {{{ +} // }}} +func SectionRename(id int, name string) (err error) { // {{{ _, err = service.Db.Conn.Exec(`UPDATE section SET name=$2 WHERE id=$1`, id, name) return -}// }}} +} // }}} +func SectionDelete(id int) (err error) { // {{{ + _, err = service.Db.Conn.Exec(`DELETE FROM public.trigger WHERE section_id = $1`, id) + if err != nil { + return werr.Wrap(err).WithData(id) + } + + _, err = service.Db.Conn.Exec(`DELETE FROM public.section WHERE id = $1`, id) + if err != nil { + return werr.Wrap(err).WithData(id) + } + return +} // }}} diff --git a/static/css/configuration.css b/static/css/configuration.css new file mode 100644 index 0000000..e013c47 --- /dev/null +++ b/static/css/configuration.css @@ -0,0 +1,130 @@ +html { + box-sizing: border-box; +} +*, +*:before, +*:after { + box-sizing: inherit; +} +*:focus { + outline: none; +} +[onClick] { + cursor: pointer; +} +html, +body { + margin: 0; + padding: 0; +} +body { + background: #282828; + font-family: sans-serif; + font-weight: 300; + color: #d5c4a1; + font-size: 11pt; +} +h1, +h2 { + margin-bottom: 4px; +} +h1:first-child, +h2:first-child { + margin-top: 0px; +} +h1 { + font-size: 1.5em; + color: #fb4934; + font-weight: 800; +} +h2 { + font-size: 1.25em; + color: #b8bb26; + font-weight: 800; +} +a { + color: #3f9da1; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +b { + font-weight: 800; +} +input[type="text"], +textarea, +select { + font-family: monospace; + background: #202020; + color: #d5c4a1; + padding: 4px 8px; + border: none; + font-size: 1em; + line-height: 1.5em; +} +button { + background: #202020; + color: #d5c4a1; + padding: 8px 32px; + border: 1px solid #535353; + font-size: 1em; + height: 3em; +} +button:focus { + background: #333; +} +.line { + grid-column: 1 / -1; + border-bottom: 1px solid #4e4e4e; +} +span.date { + color: #d5c4a1; + font-weight: 800; +} +span.time { + font-size: 0.9em; + color: #d5c4a1; +} +span.seconds { + display: none; +} +label { + user-select: none; +} +.description { + border: 1px solid #737373; + color: #3f9da1; + background: #202020; + padding: 4px 8px; + margin-top: 8px; + white-space: nowrap; + width: min-content; + border-radius: 8px; +} +#areas .area > .name { + display: grid; + grid-template-columns: 1fr min-content; + grid-gap: 0px 16px; + align-items: center; + padding-left: 16px; + padding-right: 8px; +} +#areas .area > .name img { + margin-top: 3px; + margin-bottom: 4px; + height: 16px; +} +#areas .area .section.configuration { + display: grid; + grid-template-columns: 1fr min-content; + grid-gap: 0 16px; + margin-top: 8px; + margin-bottom: 8px; +} +#areas .area .section.configuration:last-child { + margin-bottom: 16px; +} +#areas .area .section.configuration img { + height: 16px; +} diff --git a/static/css/main.css b/static/css/main.css index a213f0d..c5de4d4 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -182,10 +182,6 @@ label { margin-top: 12px; margin-bottom: 20px; } -#areas .area .section.configuration { - margin-top: 8px; - margin-bottom: 8px; -} #areas .area .section:last-child { margin-bottom: 12px; } diff --git a/static/images/delete_white.svg b/static/images/delete_white.svg new file mode 100644 index 0000000..7ae4dab --- /dev/null +++ b/static/images/delete_white.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + trash-can-outline + + + diff --git a/static/less/configuration.less b/static/less/configuration.less new file mode 100644 index 0000000..8c59755 --- /dev/null +++ b/static/less/configuration.less @@ -0,0 +1,38 @@ +@import 'theme.less'; + +#areas { + .area { + & > .name { + display: grid; + grid-template-columns: 1fr min-content; + grid-gap: 0px 16px; + align-items: center; + padding-left: 16px; + padding-right: 8px; + + img { + margin-top: 3px; + margin-bottom: 4px; + height: 16px; + } + } + + + .section.configuration { + display: grid; + grid-template-columns: 1fr min-content; + grid-gap: 0 16px; + + margin-top: 8px; + margin-bottom: 8px; + + &:last-child { + margin-bottom: 16px; + } + + img { + height: 16px; + } + } + } +} diff --git a/static/less/main.less b/static/less/main.less index de80c87..d04e244 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -94,11 +94,6 @@ margin-top: 12px; margin-bottom: 20px; - &.configuration { - margin-top: 8px; - margin-bottom: 8px; - } - &:last-child { margin-bottom: 12px; } diff --git a/views/pages/configuration.gotmpl b/views/pages/configuration.gotmpl index 0fdfd54..aecf759 100644 --- a/views/pages/configuration.gotmpl +++ b/views/pages/configuration.gotmpl @@ -1,4 +1,6 @@ {{ define "page" }} + {{ $version := .VERSION }} + {{ block "page_label" . }}{{end}} @@ -58,14 +72,21 @@
{{ range .Data.Areas }}
-
{{ .Name }}
+
+
{{ .Name }}
+ +
Create
{{ range .SortedSections }} + {{ if eq .ID 0 }} + {{ continue }} + {{ end }}
{{ .Name }}
+
{{ end }}
diff --git a/views/pages/triggers.gotmpl b/views/pages/triggers.gotmpl index a0a5bef..093a340 100644 --- a/views/pages/triggers.gotmpl +++ b/views/pages/triggers.gotmpl @@ -38,6 +38,9 @@
{{ .Name }}
{{ range .SortedSections }} + {{ if eq .ID 0 }} + {{ continue }} + {{ end }}
{{ .Name }}
From 4a52c319c43e7acc444d9489f2c19c3bbd333c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 2 Jun 2024 10:10:14 +0200 Subject: [PATCH 2/4] Clean up notification type enum and add SCRIPT --- sql/00016.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 sql/00016.sql diff --git a/sql/00016.sql b/sql/00016.sql new file mode 100644 index 0000000..9f9aad5 --- /dev/null +++ b/sql/00016.sql @@ -0,0 +1,9 @@ +ALTER TYPE notification_type RENAME TO _notification_type; +CREATE TYPE notification_type AS ENUM ('NTFY', 'SCRIPT'); + +ALTER TABLE notification RENAME COLUMN service TO _service; +ALTER TABLE notification ADD service notification_type NOT NULL DEFAULT 'NTFY'; +UPDATE notification SET service = _service::text::notification_type; + +ALTER TABLE notification DROP COLUMN _service; +DROP TYPE _notification_type; From f29693a0669c8e705e039480de62f99fbe767b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sun, 2 Jun 2024 10:59:06 +0200 Subject: [PATCH 3/4] Added script notification --- main.go | 60 +++++++++++++++++------------------ notification/factory.go | 9 ++++++ notification/script.go | 70 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 30 deletions(-) create mode 100644 notification/script.go diff --git a/main.go b/main.go index 69f80c8..c3919fa 100644 --- a/main.go +++ b/main.go @@ -120,35 +120,35 @@ func main() { // {{{ service.Register("/", false, false, staticHandler) - service.Register("/area/new/{name}", false, false, areaNew) - service.Register("/area/rename/{id}/{name}", false, false, areaRename) - service.Register("/area/delete/{id}", false, false, areaDelete) + service.Register("/area/new/{name}", false, false, actionAreaNew) + service.Register("/area/rename/{id}/{name}", false, false, actionAreaRename) + service.Register("/area/delete/{id}", false, false, actionAreaDelete) - service.Register("/section/new/{areaID}/{name}", false, false, sectionNew) - service.Register("/section/rename/{id}/{name}", false, false, sectionRename) - service.Register("/section/delete/{id}", false, false, sectionDelete) + service.Register("/section/new/{areaID}/{name}", false, false, actionSectionNew) + service.Register("/section/rename/{id}/{name}", false, false, actionSectionRename) + service.Register("/section/delete/{id}", false, false, actionSectionDelete) service.Register("/problems", false, false, pageProblems) - service.Register("/problem/acknowledge/{id}", false, false, pageProblemAcknowledge) - service.Register("/problem/unacknowledge/{id}", false, false, pageProblemUnacknowledge) + service.Register("/problem/acknowledge/{id}", false, false, actionProblemAcknowledge) + service.Register("/problem/unacknowledge/{id}", false, false, actionProblemUnacknowledge) service.Register("/datapoints", false, false, pageDatapoints) service.Register("/datapoint/edit/{id}", false, false, pageDatapointEdit) - service.Register("/datapoint/update/{id}", false, false, pageDatapointUpdate) - service.Register("/datapoint/delete/{id}", false, false, pageDatapointDelete) + service.Register("/datapoint/update/{id}", false, false, actionDatapointUpdate) + service.Register("/datapoint/delete/{id}", false, false, actionDatapointDelete) service.Register("/datapoint/values/{id}", false, false, pageDatapointValues) service.Register("/triggers", false, false, pageTriggers) - service.Register("/trigger/create/{sectionID}/{name}", false, false, triggerCreate) + service.Register("/trigger/create/{sectionID}/{name}", false, false, actionTriggerCreate) service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit) service.Register("/trigger/edit/{id}/{sectionID}", false, false, pageTriggerEdit) - service.Register("/trigger/addDatapoint/{id}/{datapointName}", false, false, pageTriggerDatapointAdd) - service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate) - service.Register("/trigger/run/{id}", false, false, pageTriggerRun) + service.Register("/trigger/addDatapoint/{id}/{datapointName}", false, false, actionTriggerDatapointAdd) + service.Register("/trigger/update/{id}", false, false, actionTriggerUpdate) + service.Register("/trigger/run/{id}", false, false, actionTriggerRun) service.Register("/trigger/delete/{id}", false, false, actionTriggerDelete) service.Register("/configuration", false, false, pageConfiguration) - service.Register("/entry/{datapoint}", false, false, entryDatapoint) + service.Register("/entry/{datapoint}", false, false, actionEntryDatapoint) go nodataLoop() @@ -199,7 +199,7 @@ func staticHandler(w http.ResponseWriter, r *http.Request, sess *session.T) { // service.StaticHandler(w, r, sess) } // }}} -func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{ +func actionEntryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { // {{{ dpoint := r.PathValue("datapoint") value, _ := io.ReadAll(r.Body) @@ -370,7 +370,7 @@ func pageIndex(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page.Render(w) } // }}} -func areaNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionAreaNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ name := r.PathValue("name") err := AreaCreate(name) if err != nil { @@ -382,7 +382,7 @@ func areaNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ w.WriteHeader(302) return } // }}} -func areaRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionAreaRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -401,7 +401,7 @@ func areaRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ w.WriteHeader(302) return } // }}} -func areaDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionAreaDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -420,7 +420,7 @@ func areaDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ return } // }}} -func sectionNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionSectionNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("areaID") areaID, err := strconv.Atoi(idStr) if err != nil { @@ -439,7 +439,7 @@ func sectionNew(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ w.WriteHeader(302) return } // }}} -func sectionRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionSectionRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -458,7 +458,7 @@ func sectionRename(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{ w.WriteHeader(302) return } // }}} -func sectionDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionSectionDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -512,7 +512,7 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page.Render(w) return } // }}} -func pageProblemAcknowledge(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionProblemAcknowledge(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -531,7 +531,7 @@ func pageProblemAcknowledge(w http.ResponseWriter, r *http.Request, _ *session.T return } // }}} -func pageProblemUnacknowledge(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionProblemUnacknowledge(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -610,7 +610,7 @@ func pageDatapointEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { / page.Render(w) return } // }}} -func pageDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -636,7 +636,7 @@ func pageDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { w.Header().Add("Location", "/datapoints") w.WriteHeader(302) } // }}} -func pageDatapointDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionDatapointDelete(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -712,7 +712,7 @@ func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page.Render(w) } // }}} -func triggerCreate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionTriggerCreate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ name := r.PathValue("name") sectionIDStr := r.PathValue("sectionID") sectionID, err := strconv.Atoi(sectionIDStr) @@ -799,7 +799,7 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // page.Render(w) } // }}} -func pageTriggerDatapointAdd(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionTriggerDatapointAdd(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ triggerID := r.PathValue("id") dpName := r.PathValue("datapointName") @@ -831,7 +831,7 @@ func pageTriggerDatapointAdd(w http.ResponseWriter, r *http.Request, _ *session. w.Header().Add("Content-Type", "application/json") w.Write(j) } // }}} -func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { @@ -866,7 +866,7 @@ func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { / w.Header().Add("Location", "/triggers") w.WriteHeader(302) } // }}} -func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ +func actionTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) if err != nil { diff --git a/notification/factory.go b/notification/factory.go index db0a4b7..90c5cfe 100644 --- a/notification/factory.go +++ b/notification/factory.go @@ -16,7 +16,16 @@ func ServiceFactory(t string, config []byte, prio int, ackURL string, logger *sl err = werr.Wrap(err).WithData(config) return nil, err } + ntfy.SetLogger(logger) return ntfy, nil + case "SCRIPT": + script, err := NewScript(config, prio, ackURL) + if err != nil { + err = werr.Wrap(err).WithData(config) + return nil, err + } + script.SetLogger(logger) + return script, nil } return nil, werr.New("Unknown notification service, '%s'", t).WithCode("002-0000") diff --git a/notification/script.go b/notification/script.go new file mode 100644 index 0000000..1e8fcca --- /dev/null +++ b/notification/script.go @@ -0,0 +1,70 @@ +package notification + +import ( + // External + werr "git.gibonuddevalla.se/go/wrappederror" + + // Standard + "encoding/json" + "log/slog" + "os/exec" + "strconv" + "strings" +) + +type Script struct { + Filename string + Prio int + AcknowledgeURL string + logger *slog.Logger +} + +func NewScript(config []byte, prio int, ackURL string) (instance *Script, err error) { + instance = new(Script) + err = json.Unmarshal(config, &instance) + if err != nil { + err = werr.Wrap(err).WithCode("002-0001").WithData(config) + return + } + instance.Prio = prio + instance.AcknowledgeURL = ackURL + return instance, nil +} + +func (script *Script) SetLogger(l *slog.Logger) { + script.logger = l +} + +func (script *Script) GetType() string { + return "SCRIPT" +} + +func (script *Script) GetPrio() int { + return script.Prio +} + +func (script Script) Send(problemID int, msg []byte) (err error) { + var errbuf strings.Builder + cmd := exec.Command(script.Filename, strconv.Itoa(problemID), script.AcknowledgeURL, string(msg)) + cmd.Stderr = &errbuf + + err = cmd.Run() + if err != nil { + script.logger.Error("notification", "type", "script", "error", err) + err = werr.Wrap(err).WithData( + struct { + Filename string + ProblemID int + Msg string + StdErr string + }{ + script.Filename, + problemID, + string(msg), + errbuf.String(), + }, + ).Log() + } + + return +} From 42b1a20531280b36c956a6c00e5fe12279ce28e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 24 Jun 2024 11:18:51 +0200 Subject: [PATCH 4/4] #3, added datapoint comment --- datapoint.go | 13 ++++-- main.go | 1 + sql/00017.sql | 1 + static/css/datapoints.css | 4 ++ static/images/info-filled.svg | 69 ++++++++++++++++++++++++++++++ static/images/info-outline.svg | 71 +++++++++++++++++++++++++++++++ static/less/datapoints.less | 6 +++ views/pages/datapoint_edit.gotmpl | 6 +++ views/pages/datapoints.gotmpl | 5 +++ 9 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 sql/00017.sql create mode 100644 static/images/info-filled.svg create mode 100644 static/images/info-outline.svg diff --git a/datapoint.go b/datapoint.go index 02acec4..084e4bf 100644 --- a/datapoint.go +++ b/datapoint.go @@ -25,6 +25,7 @@ type Datapoint struct { Group string Name string Datatype DatapointType + Comment string LastValue time.Time `db:"last_value"` DatapointValueJSON []byte `db:"datapoint_value_json"` LastDatapointValue DatapointValue @@ -73,11 +74,12 @@ func (dp Datapoint) Update() (err error) { // {{{ if dp.ID == 0 { _, err = service.Db.Conn.Exec( - `INSERT INTO datapoint("group", name, datatype, nodata_problem_seconds) VALUES($1, $2, $3, $4)`, + `INSERT INTO datapoint("group", name, datatype, nodata_problem_seconds, comment) VALUES($1, $2, $3, $4, $5)`, dp.Group, name, dp.Datatype, dp.NodataProblemSeconds, + dp.Comment, ) } else { /* Keep nodata_is_problem as is unless the nodata_problem_seconds is changed. @@ -90,10 +92,11 @@ func (dp Datapoint) Update() (err error) { // {{{ "group"=$2, name=$3, datatype=$4, - nodata_problem_seconds=$5, + comment=$5, + nodata_problem_seconds=$6, nodata_is_problem = ( CASE - WHEN $5 != nodata_problem_seconds THEN false + WHEN $6 != nodata_problem_seconds THEN false ELSE nodata_is_problem END @@ -105,6 +108,7 @@ func (dp Datapoint) Update() (err error) { // {{{ dp.Group, name, dp.Datatype, + dp.Comment, dp.NodataProblemSeconds, ) } @@ -161,6 +165,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.datatype, dp.last_value, dp.group, + dp.comment, dp.nodata_problem_seconds, dpv.id AS v_id, @@ -190,6 +195,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ Group string Name string Datatype DatapointType + Comment string LastValue time.Time `db:"last_value"` NodataProblemSeconds int `db:"nodata_problem_seconds"` @@ -214,6 +220,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.Name = res.Name dp.Group = res.Group dp.Datatype = res.Datatype + dp.Comment = res.Comment dp.LastValue = res.LastValue dp.Found = true dp.NodataProblemSeconds = res.NodataProblemSeconds diff --git a/main.go b/main.go index c3919fa..569d99f 100644 --- a/main.go +++ b/main.go @@ -626,6 +626,7 @@ func actionDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) dp.Group = r.FormValue("group") dp.Name = r.FormValue("name") dp.Datatype = DatapointType(r.FormValue("datatype")) + dp.Comment = r.FormValue("comment") dp.NodataProblemSeconds = nodataSeconds err = dp.Update() if err != nil { diff --git a/sql/00017.sql b/sql/00017.sql new file mode 100644 index 0000000..8603963 --- /dev/null +++ b/sql/00017.sql @@ -0,0 +1 @@ +ALTER TABLE public.datapoint ADD COLUMN comment VARCHAR DEFAULT '' NOT NULL; diff --git a/static/css/datapoints.css b/static/css/datapoints.css index 0057c02..3baa3b0 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -125,12 +125,16 @@ label { } #datapoints div { white-space: nowrap; + align-self: center; } #datapoints .icons { display: flex; gap: 12px; align-items: center; } +#datapoints img.info { + height: 20px; +} #values { display: grid; grid-template-columns: repeat(2, min-content); diff --git a/static/images/info-filled.svg b/static/images/info-filled.svg new file mode 100644 index 0000000..71e7cb2 --- /dev/null +++ b/static/images/info-filled.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + information-slab-circle + information + + + diff --git a/static/images/info-outline.svg b/static/images/info-outline.svg new file mode 100644 index 0000000..e53a3ce --- /dev/null +++ b/static/images/info-outline.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + information-slab-circle + information-slab-circle-outline + information-outline + + + diff --git a/static/less/datapoints.less b/static/less/datapoints.less index b8ce8c5..8ecc09a 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -13,6 +13,7 @@ margin-top: 1.5em; padding-bottom: 4px; } + h2 { border-bottom: unset; } @@ -26,6 +27,7 @@ div { white-space: nowrap; + align-self: center; } .icons { @@ -33,6 +35,10 @@ gap: 12px; align-items: center; } + + img.info { + height: 20px; + } } #values { diff --git a/views/pages/datapoint_edit.gotmpl b/views/pages/datapoint_edit.gotmpl index 9d65b50..12b2fc2 100644 --- a/views/pages/datapoint_edit.gotmpl +++ b/views/pages/datapoint_edit.gotmpl @@ -32,6 +32,12 @@
Set to 0 to disable.
+
Comment
+
+ +
+ +
{{ if eq .Data.Datapoint.ID 0 }} diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl index 1300e55..aa3327b 100644 --- a/views/pages/datapoints.gotmpl +++ b/views/pages/datapoints.gotmpl @@ -31,6 +31,11 @@
{{ .LastDatapointValue.Value }}
{{ end }}
+ {{ if eq .Comment "" }} +
+ {{ else }} +
+ {{ end }}