From 1b888a3513bec162797939cfa1aa6d720e7eb668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 1 May 2024 21:34:57 +0200 Subject: [PATCH 001/152] Fixed bug with funcmap in prod mode --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index ae61193..0a68557 100644 --- a/main.go +++ b/main.go @@ -264,7 +264,7 @@ func getPage(layout, page string) (tmpl *template.Template, err error) { // {{{ if flagDev { tmpl, err = template.New("main.gotmpl").Funcs(funcMap).ParseFS(os.DirFS("."), filenames...) } else { - tmpl, err = template.New("main.gotmpl").ParseFS(viewFS, filenames...) + tmpl, err = template.New("main.gotmpl").Funcs(funcMap).ParseFS(viewFS, filenames...) } if err != nil { err = we.Wrap(err).Log() From 715ac03c3b68e86c8f75ca1c72a9324024c7da0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 1 May 2024 21:35:20 +0200 Subject: [PATCH 002/152] Bumped to v3 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 0a68557..5384cb2 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "time" ) -const VERSION = "v2" +const VERSION = "v3" var ( logger *slog.Logger From 9d2bfc599dfa24d87f2cb7759432eb384db049bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 1 May 2024 21:44:53 +0200 Subject: [PATCH 003/152] Fixed problems with empty tables, bumped to v4 --- area.go | 4 ++++ main.go | 2 +- problem.go | 4 ++++ trigger.go | 4 ++++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/area.go b/area.go index a001f00..8d95156 100644 --- a/area.go +++ b/area.go @@ -45,6 +45,10 @@ func AreaRetrieve() (areas []Area, err error) { // {{{ return } + if jsonData == nil { + return + } + err = json.Unmarshal(jsonData, &areas) if err != nil { err = re.Wrap(err) diff --git a/main.go b/main.go index 5384cb2..bc8aaeb 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "time" ) -const VERSION = "v3" +const VERSION = "v4" var ( logger *slog.Logger diff --git a/problem.go b/problem.go index 5d6f0b5..67717dd 100644 --- a/problem.go +++ b/problem.go @@ -55,6 +55,10 @@ func ProblemsRetrieve() (problems []Problem, err error) { return } + if jsonBody == nil { + return + } + err = json.Unmarshal(jsonBody, &problems) if err != nil { err = we.Wrap(err) diff --git a/trigger.go b/trigger.go index d7273ad..01d6043 100644 --- a/trigger.go +++ b/trigger.go @@ -63,6 +63,10 @@ func TriggersRetrieve() (areas []Area, err error) { // {{{ return } + if jsonData == nil { + return + } + err = json.Unmarshal(jsonData, &areas) if err != nil { err = we.Wrap(err) From 14ed83d227b172627dde70ebcb7f8cbf63146dad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 1 May 2024 22:34:34 +0200 Subject: [PATCH 004/152] Added readme --- README.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd6476a --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +# Configuration + +```yaml +network: + address: "[::]" + port: 9000 + +websocket: + domains: + - localhost + - smon.example.com + +database: + host: localhost + port: 5432 + name: smon + username: smon + password: you_wish + +session: + daysvalid: 31 + +application: + logfile: /var/log/smon.log +``` From e549e18d44b524fa5eb55f58fb10b9feda6b6975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 1 May 2024 20:39:55 +0000 Subject: [PATCH 005/152] Update README.md --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index bd6476a..618fc5c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# Quick start + +1) Create an empty database +1) Create the configuration file (default ~/.config/smon.yaml) +1) Run ./smon (will create the database schema) + # Configuration ```yaml @@ -23,3 +29,7 @@ session: application: logfile: /var/log/smon.log ``` + +# Data from systems to datapoints + +`curl -d "$(time +'%F %T +02')" http://localhost:9000/entry/datapoint_name` \ No newline at end of file From baeeced87b59549c31b8066769f72be954525830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 2 May 2024 08:59:55 +0200 Subject: [PATCH 006/152] Implemented datapoint delete --- datapoint.go | 7 ++++ main.go | 18 ++++++++++ static/css/datapoints.css | 2 +- static/images/delete.svg | 67 +++++++++++++++++++++++++++++++++++ static/less/datapoints.less | 2 +- views/pages/datapoints.gotmpl | 8 +++-- 6 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 static/images/delete.svg diff --git a/datapoint.go b/datapoint.go index 9e925ad..a4b556b 100644 --- a/datapoint.go +++ b/datapoint.go @@ -233,3 +233,10 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{ return } // }}} +func DatapointDelete(id int) (err error) {// {{{ + _, err = service.Db.Conn.Exec(`DELETE FROM datapoint WHERE id=$1`, id) + if err != nil { + err = we.Wrap(err).WithData(id) + } + return +}// }}} diff --git a/main.go b/main.go index bc8aaeb..c19f337 100644 --- a/main.go +++ b/main.go @@ -111,6 +111,7 @@ func main() { // {{{ 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("/triggers", false, false, pageTriggers) service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit) @@ -491,6 +492,23 @@ 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) { // {{{ + idStr := r.PathValue("id") + id, err := strconv.Atoi(idStr) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + + err = DatapointDelete(id) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + + w.Header().Add("Location", "/datapoints") + w.WriteHeader(302) +} // }}} func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ areas, err := TriggersRetrieve() diff --git a/static/css/datapoints.css b/static/css/datapoints.css index 35f613e..0713253 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -99,7 +99,7 @@ label { } #datapoints { display: grid; - grid-template-columns: repeat(4, min-content); + grid-template-columns: repeat(5, min-content); grid-gap: 8px 16px; margin-top: 16px; } diff --git a/static/images/delete.svg b/static/images/delete.svg new file mode 100644 index 0000000..23ba519 --- /dev/null +++ b/static/images/delete.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + trash-can-outline + + + diff --git a/static/less/datapoints.less b/static/less/datapoints.less index 2bffef9..7483cc9 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -2,7 +2,7 @@ #datapoints { display: grid; - grid-template-columns: repeat(4, min-content); + grid-template-columns: repeat(5, min-content); grid-gap: 8px 16px; margin-top: 16px; diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl index a3c073f..50e3727 100644 --- a/views/pages/datapoints.gotmpl +++ b/views/pages/datapoints.gotmpl @@ -1,5 +1,6 @@ {{ define "page" }} - + {{ $version := .VERSION }} + {{ block "page_label" . }}{{end}} @@ -10,6 +11,7 @@
Datatype
Last value
Value
+
{{ range .Data.Datapoints }}
@@ -20,8 +22,8 @@
{{ if .LastDatapointValue.ValueDateTime.Valid }}{{ format_time .LastDatapointValue.Value }}{{ end }}
{{ else }}
{{ .LastDatapointValue.Value }}
- {{ end }} + {{ end }} +
{{ end }} - {{ end }} From ac7ffcda23590ba805e99d26ffe7c58ae271c839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 2 May 2024 09:00:11 +0200 Subject: [PATCH 007/152] Bumped to v5 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c19f337..086797f 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "time" ) -const VERSION = "v4" +const VERSION = "v5" var ( logger *slog.Logger From 7990f1f419b6b88153c016667303d6d9ce2e8b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 4 May 2024 22:07:41 +0200 Subject: [PATCH 008/152] Remove datapoint from trigger --- datapoint.go | 15 +++++++++++++-- static/css/trigger_edit.css | 9 ++++++++- static/js/trigger_edit.mjs | 23 +++++++++++++++++++---- static/less/trigger_edit.less | 11 ++++++++++- views/pages/trigger_edit.gotmpl | 5 ++--- 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/datapoint.go b/datapoint.go index a4b556b..7d9bc8f 100644 --- a/datapoint.go +++ b/datapoint.go @@ -27,6 +27,7 @@ type Datapoint struct { LastValue time.Time `db:"last_value"` DatapointValueJSON []byte `db:"datapoint_value_json"` LastDatapointValue DatapointValue + Found bool } type DatapointValue struct { @@ -177,6 +178,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.Name = res.Name dp.Datatype = res.Datatype dp.LastValue = res.LastValue + dp.Found = true if res.VID.Valid { dpv.ID = int(res.VID.Int64) @@ -196,16 +198,25 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{ var query string var param any if id > 0 { - query = `SELECT * FROM datapoint WHERE id = $1` + query = `SELECT *, true AS found FROM datapoint WHERE id = $1` param = id dp.ID = id } else { - query = `SELECT * FROM datapoint WHERE name = $1` + query = `SELECT *, true AS found FROM datapoint WHERE name = $1` param = name } row := service.Db.Conn.QueryRowx(query, param) err = row.StructScan(&dp) + + if err == sql.ErrNoRows { + dp = Datapoint{ + Name: name, + } + err = nil + return + } + if err != nil { err = we.Wrap(err).WithData(name) return diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index af41402..477f462 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -112,9 +112,16 @@ label { .widgets .datapoints { font: "Roboto Mono", monospace; display: grid; - grid-template-columns: min-content 1fr; + grid-template-columns: min-content min-content 1fr; gap: 6px 8px; margin-bottom: 8px; + white-space: nowrap; +} +.widgets .datapoints .invalid { + color: #c83737; +} +.widgets .datapoints .delete img { + height: 16px; } .widgets .action { display: grid; diff --git a/static/js/trigger_edit.mjs b/static/js/trigger_edit.mjs index e534562..d190ef1 100644 --- a/static/js/trigger_edit.mjs +++ b/static/js/trigger_edit.mjs @@ -1,5 +1,5 @@ export class UI { - constructor() {//{{{ + constructor(version) {//{{{ document.getElementById('button-run'). addEventListener('click', evt => evt.preventDefault()) @@ -7,6 +7,7 @@ export class UI { document.querySelector('input[name="name"]').focus() + this.version = version this.datapoints = [] }//}}} render() {//{{{ @@ -17,8 +18,9 @@ export class UI { let html = Object.keys(this.trigger.datapoints).sort().map(dpName => { const dp = this.trigger.datapoints[dpName] return ` -
${dp.Name}
-
${dp.LastDatapointValue.TemplateValue}
+
+
${dp.Name}
+
${dp.Found ? dp.LastDatapointValue.TemplateValue : ''}
` }).join('') datapoints.innerHTML += html @@ -78,7 +80,15 @@ export class UI { dlg.close() this.render() }//}}} - update() {//{{{ + deleteDatapoint(name) {//{{{ + if (!confirm(`Delete ${name}?`)) { + return + } + + delete this.trigger.datapoints[name] + _ui.update(true) + }//}}} + update(stayOnSamePage) {//{{{ const form = document.getElementById('form-trigger') var formData = new FormData(form) Object.keys(this.trigger.datapoints).forEach(name => formData.append("datapoints[]", name)) @@ -87,6 +97,11 @@ export class UI { body: formData, }) .then(resp => { + if (stayOnSamePage) { + location.reload() + return + } + if (resp.redirected) { location.href = resp.url return diff --git a/static/less/trigger_edit.less b/static/less/trigger_edit.less index 52eb281..8e48ed9 100644 --- a/static/less/trigger_edit.less +++ b/static/less/trigger_edit.less @@ -19,9 +19,18 @@ .datapoints { font: "Roboto Mono", monospace; display: grid; - grid-template-columns: min-content 1fr; + grid-template-columns: min-content min-content 1fr; gap: 6px 8px; margin-bottom: 8px; + white-space: nowrap; + + .invalid { + color: #c83737; + } + + .delete img { + height: 16px; + } } .action { diff --git a/views/pages/trigger_edit.gotmpl b/views/pages/trigger_edit.gotmpl index 6be6a08..f0c743d 100644 --- a/views/pages/trigger_edit.gotmpl +++ b/views/pages/trigger_edit.gotmpl @@ -2,7 +2,7 @@ + + + +
diff --git a/views/pages/datapoint_values.gotmpl b/views/pages/datapoint_values.gotmpl index fe4651c..f568b95 100644 --- a/views/pages/datapoint_values.gotmpl +++ b/views/pages/datapoint_values.gotmpl @@ -2,8 +2,19 @@ {{ $version := .VERSION }} + + {{ block "page_label" . }}{{end}} +
+ +
+
{{ range .Data.Values }}
{{ format_time .Ts }}
From 7724c742ad8dfc1f80d41df3aab70afc622164fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 20 May 2024 13:21:22 +0200 Subject: [PATCH 016/152] Version parameter --- main.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/main.go b/main.go index 5a635fe..e191dd5 100644 --- a/main.go +++ b/main.go @@ -32,6 +32,7 @@ var ( logger *slog.Logger flagConfigFile string flagDev bool + flagVersion bool service *ws.Service logFile *os.File parsedTemplates map[string]*template.Template @@ -59,6 +60,7 @@ func init() { // {{{ cfgPath := path.Join(confDir, "smon.yaml") flag.StringVar(&flagConfigFile, "config", cfgPath, "Path and filename of the YAML configuration file") flag.BoolVar(&flagDev, "dev", false, "reload templates on each request") + flag.BoolVar(&flagVersion, "version", false, "Display version and exit") flag.Parse() componentFilenames, err = getComponentFilenames(viewFS) @@ -70,6 +72,11 @@ func init() { // {{{ } // }}} func main() { // {{{ + if flagVersion { + fmt.Println(VERSION) + os.Exit(0) + } + var err error werr.Init() From e4dbabfea5b2a40c41dcd9342f1624c2490e882b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 20 May 2024 18:57:47 +0200 Subject: [PATCH 017/152] Fixed last updated value on datapoints --- datapoint.go | 2 ++ main.go | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/datapoint.go b/datapoint.go index 055c7a3..f02dfb8 100644 --- a/datapoint.go +++ b/datapoint.go @@ -117,6 +117,8 @@ func DatapointAdd[T any](name string, value T) (err error) { // {{{ return } + service.Db.Conn.Exec(`UPDATE datapoint SET last_value = NOW() WHERE name=$1`, name) + return } // }}} diff --git a/main.go b/main.go index e191dd5..762ae72 100644 --- a/main.go +++ b/main.go @@ -268,7 +268,7 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { / WHERE service=$1 AND prio=$2 - `, + `, (*notificationService).GetType(), (*notificationService).GetPrio(), problemID, From 63956de837f5cf608524df892ebfac69f63a2029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 20 May 2024 18:58:17 +0200 Subject: [PATCH 018/152] Bumped to v9 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 762ae72..70192db 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" ) -const VERSION = "v8" +const VERSION = "v9" var ( logger *slog.Logger From d3d6d5d13d086992d927838a87c9efaec8967b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 20 May 2024 19:40:19 +0200 Subject: [PATCH 019/152] Added groups for datapoints --- datapoint.go | 11 +++++++++-- main.go | 1 + sql/00013.sql | 1 + static/css/datapoints.css | 10 ++++++++++ static/less/datapoints.less | 12 ++++++++++++ views/pages/datapoint_edit.gotmpl | 3 +++ views/pages/datapoints.gotmpl | 18 ++++++++++++------ 7 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 sql/00013.sql diff --git a/datapoint.go b/datapoint.go index f02dfb8..07346e8 100644 --- a/datapoint.go +++ b/datapoint.go @@ -22,6 +22,7 @@ const ( type Datapoint struct { ID int + Group string Name string Datatype DatapointType LastValue time.Time `db:"last_value"` @@ -70,14 +71,16 @@ func (dp Datapoint) Update() (err error) {// {{{ if dp.ID == 0 { _, err = service.Db.Conn.Exec( - `INSERT INTO datapoint(name, datatype) VALUES($1, $2)`, + `INSERT INTO datapoint("group", name, datatype) VALUES($1, $2, $3)`, + dp.Group, name, dp.Datatype, ) } else { _, err = service.Db.Conn.Exec( - `UPDATE datapoint SET name=$2, datatype=$3 WHERE id=$1`, + `UPDATE datapoint SET "group"=$2, name=$3, datatype=$4 WHERE id=$1`, dp.ID, + dp.Group, name, dp.Datatype, ) @@ -131,6 +134,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.name, dp.datatype, dp.last_value, + dp.group, dpv.id AS v_id, dpv.ts, @@ -146,6 +150,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ FROM datapoint_value ) dpv ON dpv.datapoint_id = dp.id AND rn = 1 ORDER BY + dp.group ASC, dp.name ASC `) if err != nil { @@ -155,6 +160,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ type DbRes struct { ID int + Group string Name string Datatype DatapointType LastValue time.Time `db:"last_value"` @@ -178,6 +184,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.ID = res.ID dp.Name = res.Name + dp.Group = res.Group dp.Datatype = res.Datatype dp.LastValue = res.LastValue dp.Found = true diff --git a/main.go b/main.go index 70192db..383ede6 100644 --- a/main.go +++ b/main.go @@ -555,6 +555,7 @@ func pageDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { var dp Datapoint dp.ID = id + dp.Group = r.FormValue("group") dp.Name = r.FormValue("name") dp.Datatype = DatapointType(r.FormValue("datatype")) err = dp.Update() diff --git a/sql/00013.sql b/sql/00013.sql new file mode 100644 index 0000000..0fae712 --- /dev/null +++ b/sql/00013.sql @@ -0,0 +1 @@ +ALTER TABLE datapoint ADD COLUMN "group" varchar NOT NULL DEFAULT ''; diff --git a/static/css/datapoints.css b/static/css/datapoints.css index 82b89a2..1963b13 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -103,8 +103,18 @@ label { grid-gap: 8px 16px; margin-top: 16px; } +#datapoints .group { + font-size: 1.1em; + font-weight: bold; + color: #fabd2f; + margin-top: 1.5em; + padding-bottom: 4px; + border-bottom: unset; +} #datapoints .header { font-weight: 500; + font-size: 0.85em; + color: #d5c4a1; } #datapoints div { white-space: nowrap; diff --git a/static/less/datapoints.less b/static/less/datapoints.less index db152b9..26ee63d 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -6,8 +6,20 @@ grid-gap: 8px 16px; margin-top: 16px; + .group { + font-size: 1.10em; + font-weight: bold; + color: @color2; + margin-top: 1.5em; + padding-bottom: 4px; + border-bottom: unset; + } + + .header { font-weight: @bold; + font-size: 0.85em; + color: @text1; } div { diff --git a/views/pages/datapoint_edit.gotmpl b/views/pages/datapoint_edit.gotmpl index 439b4b2..9e9ee66 100644 --- a/views/pages/datapoint_edit.gotmpl +++ b/views/pages/datapoint_edit.gotmpl @@ -10,6 +10,9 @@
+
Group
+
+
Name
diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl index a8dbf1c..91fc6c1 100644 --- a/views/pages/datapoints.gotmpl +++ b/views/pages/datapoints.gotmpl @@ -7,14 +7,19 @@ Create
-
Name
-
Datatype
-
Last value
-
Value
-
- + {{ $prevGroup := "kalle" }} {{ range .Data.Datapoints }} + {{ if ne $prevGroup .Group }} +
{{ .Group }}
+
Name
+
Datatype
+
Last value
+
Value
+
+ + {{ else }}
+ {{ end }}
{{ .Datatype }}
{{ format_time .LastValue }}
@@ -27,6 +32,7 @@
+ {{ $prevGroup = .Group }} {{ end }}
{{ end }} From 458769c6e59476ba873633be803755f766351d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 20 May 2024 19:41:24 +0200 Subject: [PATCH 020/152] Bumped to v10 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 383ede6..38e55e1 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" ) -const VERSION = "v9" +const VERSION = "v10" var ( logger *slog.Logger From 211e4978f3c6067edf7234d2bbc623572cfb3e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 22 May 2024 07:32:53 +0200 Subject: [PATCH 021/152] UI changes, bumped to v11 --- main.go | 2 +- static/css/main.css | 35 +++++++++++++--- static/images/datapoints.svg | 51 ++++++++++++++++++----- static/images/datapoints_selected.svg | 59 ++++++++++++++++++++------- static/less/main.less | 37 ++++++++++++++--- views/components/menu.gotmpl | 9 ++++ 6 files changed, 157 insertions(+), 36 deletions(-) diff --git a/main.go b/main.go index 38e55e1..883457f 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" ) -const VERSION = "v10" +const VERSION = "v11" var ( logger *slog.Logger diff --git a/static/css/main.css b/static/css/main.css index 039c547..7d52a1c 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -100,14 +100,29 @@ label { #layout { display: grid; grid-template-areas: "menu content"; - grid-template-columns: 64px 1fr; + grid-template-columns: 96px 1fr; height: 100vh; } #menu { - display: flex; - flex-flow: column wrap; - justify-content: flex-start; - gap: 24px; + display: grid; + grid-template-columns: min-content; + grid-template-rows: 38px + 48px + + 38px + 48px + + 38px + 48px + + 38px + 48px + + 38px + 48px + ; + justify-items: center; + align-items: start; grid-area: menu; background: #202020; padding: 16px; @@ -116,6 +131,16 @@ label { display: block; width: 32px; } +#menu .label { + font-size: 0.9em; + font-weight: bold; +} +#menu .label a { + color: #777; +} +#menu .label.selected a { + color: #f7edd7; +} #page { grid-area: content; padding: 32px; diff --git a/static/images/datapoints.svg b/static/images/datapoints.svg index 652e18f..d28f210 100644 --- a/static/images/datapoints.svg +++ b/static/images/datapoints.svg @@ -2,9 +2,9 @@ + inkscape:current-layer="layer1" + showgrid="false" /> chart-timeline-variant + style="fill:#777777;fill-opacity:1;stroke-width:0.384844" /> + + + + + diff --git a/static/images/datapoints_selected.svg b/static/images/datapoints_selected.svg index 49b417c..51b21a7 100644 --- a/static/images/datapoints_selected.svg +++ b/static/images/datapoints_selected.svg @@ -2,13 +2,13 @@ + inkscape:current-layer="layer1" + showgrid="false" /> + transform="translate(-102.9229,-148.76801)"> file-chart + chart-timeline-variant + style="fill:#777777;fill-opacity:1;stroke-width:0.384844" /> + + + + + diff --git a/static/less/main.less b/static/less/main.less index 979e0f8..6900502 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -3,15 +3,31 @@ #layout { display: grid; grid-template-areas: "menu content"; - grid-template-columns: 64px 1fr; + grid-template-columns: 96px 1fr; height: 100vh; } #menu { - display: flex; - flex-flow: column wrap; - justify-content: flex-start; - gap: 24px; + display: grid; + grid-template-columns: min-content; + grid-template-rows: + 38px + 48px + + 38px + 48px + + 38px + 48px + + 38px + 48px + + 38px + 48px + ; + justify-items: center; + align-items: start; grid-area: menu; background: @bg2; @@ -21,6 +37,17 @@ display: block; width: 32px; } + + .label { + font-size: 0.9em; + font-weight: bold; + + a { color: #777; } + + &.selected { + a { color: @text2; } + } + } } #page { diff --git a/views/components/menu.gotmpl b/views/components/menu.gotmpl index 68144e1..2e75012 100644 --- a/views/components/menu.gotmpl +++ b/views/components/menu.gotmpl @@ -1,9 +1,18 @@ {{ define "menu" }} {{ end }} From 37c72f3ab1c36680133d6bfe17c8016bda8db710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 22 May 2024 07:49:50 +0200 Subject: [PATCH 022/152] Updated menu UI --- static/css/main.css | 46 +++++++++++++--------------- static/less/main.less | 58 +++++++++++++++++------------------- views/components/menu.gotmpl | 40 ++++++++++++++++++------- 3 files changed, 77 insertions(+), 67 deletions(-) diff --git a/static/css/main.css b/static/css/main.css index 7d52a1c..3f77c8b 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -105,42 +105,36 @@ label { } #menu { display: grid; - grid-template-columns: min-content; - grid-template-rows: 38px - 48px - - 38px - 48px - - 38px - 48px - - 38px - 48px - - 38px - 48px - ; - justify-items: center; + grid-template-columns: 1fr; + grid-template-rows: repeat(32, min-content); align-items: start; grid-area: menu; background: #202020; - padding: 16px; } -#menu img { +#menu .entry.selected { + background: #333; +} +#menu .entry.selected a { + color: #f7edd7 !important; +} +#menu .entry > a { + display: grid; + justify-items: center; + grid-template-rows: 38px + 16px + ; + padding: 16px; + color: #777; + text-decoration: none; +} +#menu .entry > a img { display: block; width: 32px; } -#menu .label { +#menu .entry > a .label { font-size: 0.9em; font-weight: bold; } -#menu .label a { - color: #777; -} -#menu .label.selected a { - color: #f7edd7; -} #page { grid-area: content; padding: 32px; diff --git a/static/less/main.less b/static/less/main.less index 6900502..099162d 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -9,43 +9,39 @@ #menu { display: grid; - grid-template-columns: min-content; - grid-template-rows: - 38px - 48px - - 38px - 48px - - 38px - 48px - - 38px - 48px - - 38px - 48px - ; - justify-items: center; + grid-template-columns: 1fr; + grid-template-rows: repeat(32, min-content); align-items: start; grid-area: menu; background: @bg2; - padding: 16px; - - img { - display: block; - width: 32px; - } - - .label { - font-size: 0.9em; - font-weight: bold; - - a { color: #777; } + .entry { &.selected { - a { color: @text2; } + background: @bg3; + a { color: @text2 !important; } + } + + &>a { + display: grid; + justify-items: center; + grid-template-rows: + 38px + 16px + ; + padding: 16px; + color: #777; + text-decoration: none; + + img { + display: block; + width: 32px; + } + + .label { + font-size: 0.9em; + font-weight: bold; + } } } } diff --git a/views/components/menu.gotmpl b/views/components/menu.gotmpl index 2e75012..f00b4d6 100644 --- a/views/components/menu.gotmpl +++ b/views/components/menu.gotmpl @@ -1,18 +1,38 @@ {{ define "menu" }} {{ end }} From 718610c3fb4b6081c058b0a6b9c4c2e09b983bf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Wed, 22 May 2024 07:59:33 +0200 Subject: [PATCH 023/152] Updated webservice dep --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3626f2e..d8dc4da 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module smon go 1.22.0 require ( - git.gibonuddevalla.se/go/webservice v0.2.12 + git.gibonuddevalla.se/go/webservice v0.2.15 git.gibonuddevalla.se/go/wrappederror v0.3.4 github.com/expr-lang/expr v1.16.5 ) From 6629b834fd8ce1a17064951690b416de81016fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 23 May 2024 10:16:21 +0200 Subject: [PATCH 024/152] Small UI fix --- static/css/main.css | 5 +++++ static/less/main.less | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/static/css/main.css b/static/css/main.css index 3f77c8b..e24674f 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -174,6 +174,11 @@ label { } #areas .area .section { margin: 8px 16px; + margin-top: 12px; + margin-bottom: 20px; +} +#areas .area .section:last-child { + margin-bottom: 12px; } #areas .area .section .create { display: grid; diff --git a/static/less/main.less b/static/less/main.less index 099162d..baf874c 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -91,6 +91,12 @@ .section { margin: 8px 16px; + margin-top: 12px; + margin-bottom: 20px; + + &:last-child { + margin-bottom: 12px; + } .create { display: grid; From b22e99a072fbbc29f8f8e9785eb1d7cc37ec64ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 25 May 2024 07:49:48 +0200 Subject: [PATCH 025/152] Removed graph plotting data --- views/pages/datapoint_values.gotmpl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/views/pages/datapoint_values.gotmpl b/views/pages/datapoint_values.gotmpl index f568b95..fe4651c 100644 --- a/views/pages/datapoint_values.gotmpl +++ b/views/pages/datapoint_values.gotmpl @@ -2,19 +2,8 @@ {{ $version := .VERSION }} - - {{ block "page_label" . }}{{end}} -
- -
-
{{ range .Data.Values }}
{{ format_time .Ts }}
From 1185ebd030162f28904ef0d0772df8a706fa938f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 25 May 2024 09:40:40 +0200 Subject: [PATCH 026/152] Added nodata code --- datapoint.go | 64 ++++++++++++++++++-------- main.go | 6 +++ nodata.go | 75 +++++++++++++++++++++++++++++++ sql/00014.sql | 4 ++ views/pages/datapoint_edit.gotmpl | 7 +++ 5 files changed, 137 insertions(+), 19 deletions(-) create mode 100644 nodata.go create mode 100644 sql/00014.sql diff --git a/datapoint.go b/datapoint.go index 07346e8..f851a5b 100644 --- a/datapoint.go +++ b/datapoint.go @@ -21,14 +21,16 @@ const ( ) type Datapoint struct { - ID int - Group string - Name string - Datatype DatapointType - LastValue time.Time `db:"last_value"` - DatapointValueJSON []byte `db:"datapoint_value_json"` - LastDatapointValue DatapointValue - Found bool + ID int + Group string + Name string + Datatype DatapointType + LastValue time.Time `db:"last_value"` + DatapointValueJSON []byte `db:"datapoint_value_json"` + LastDatapointValue DatapointValue + Found bool + NodataProblemSeconds int `db:"nodata_problem_seconds"` + NodataIsProblem bool `db:"nodata_is_problem"` } type DatapointValue struct { @@ -56,13 +58,13 @@ func (dp DatapointValue) Value() any { // {{{ return nil } // }}} -func (dp DatapointValue) FormattedTime() string {// {{{ +func (dp DatapointValue) FormattedTime() string { // {{{ if dp.ValueDateTime.Valid { return dp.ValueDateTime.Time.Format("2006-01-02 15:04:05") } return "invalid time" -}// }}} -func (dp Datapoint) Update() (err error) {// {{{ +} // }}} +func (dp Datapoint) Update() (err error) { // {{{ name := strings.TrimSpace(dp.Name) if name == "" { err = errors.New("Name can't be empty") @@ -71,23 +73,47 @@ func (dp Datapoint) Update() (err error) {// {{{ if dp.ID == 0 { _, err = service.Db.Conn.Exec( - `INSERT INTO datapoint("group", name, datatype) VALUES($1, $2, $3)`, + `INSERT INTO datapoint("group", name, datatype) VALUES($1, $2, $3, $4)`, dp.Group, name, dp.Datatype, + dp.NodataProblemSeconds, ) } else { + /* Keep nodata_is_problem as is unless the nodata_problem_seconds is changed. + * Otherwise unnecessary nodata problems could be notified when updating unrelated + * datapoint properties. */ _, err = service.Db.Conn.Exec( - `UPDATE datapoint SET "group"=$2, name=$3, datatype=$4 WHERE id=$1`, + ` + UPDATE datapoint + SET + "group"=$2, + name=$3, + datatype=$4, + nodata_problem_seconds=$5, + nodata_is_problem = ( + CASE + WHEN $5 != nodata_problem_seconds THEN false + ELSE + nodata_is_problem + END + ) + WHERE + id=$1 + `, dp.ID, dp.Group, name, dp.Datatype, + dp.NodataProblemSeconds, ) } + if err != nil { + err = werr.Wrap(err) + } return -}// }}} +} // }}} func DatapointAdd[T any](name string, value T) (err error) { // {{{ row := service.Db.Conn.QueryRow(`SELECT id, datatype FROM datapoint WHERE name=$1`, name) @@ -120,7 +146,7 @@ func DatapointAdd[T any](name string, value T) (err error) { // {{{ return } - service.Db.Conn.Exec(`UPDATE datapoint SET last_value = NOW() WHERE name=$1`, name) + service.Db.Conn.Exec(`UPDATE datapoint SET last_value = NOW(), nodata_is_problem = false WHERE id=$1`, dpID) return } // }}} @@ -253,14 +279,14 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{ return } // }}} -func DatapointDelete(id int) (err error) {// {{{ +func DatapointDelete(id int) (err error) { // {{{ _, err = service.Db.Conn.Exec(`DELETE FROM datapoint WHERE id=$1`, id) if err != nil { err = werr.Wrap(err).WithData(id) } return -}// }}} -func DatapointValues(id int) (values []DatapointValue, err error) {// {{{ +} // }}} +func DatapointValues(id int) (values []DatapointValue, err error) { // {{{ rows, err := service.Db.Conn.Queryx(`SELECT * FROM datapoint_value WHERE datapoint_id=$1 ORDER BY ts DESC LIMIT 500`, id) if err != nil { err = werr.Wrap(err).WithData(id) @@ -278,4 +304,4 @@ func DatapointValues(id int) (values []DatapointValue, err error) {// {{{ values = append(values, dpv) } return -}// }}} +} // }}} diff --git a/main.go b/main.go index 883457f..c0f557b 100644 --- a/main.go +++ b/main.go @@ -140,6 +140,8 @@ func main() { // {{{ service.Register("/configuration", false, false, pageConfiguration) service.Register("/entry/{datapoint}", false, false, entryDatapoint) + go nodataLoop() + err = service.Start() if err != nil { logger.Error("webserver", "error", werr.Wrap(err)) @@ -553,11 +555,15 @@ func pageDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { return } + var nodataSeconds int + nodataSeconds, _ = strconv.Atoi(r.FormValue("nodata_seconds")) + var dp Datapoint dp.ID = id dp.Group = r.FormValue("group") dp.Name = r.FormValue("name") dp.Datatype = DatapointType(r.FormValue("datatype")) + dp.NodataProblemSeconds = nodataSeconds err = dp.Update() if err != nil { httpError(w, werr.Wrap(err).Log()) diff --git a/nodata.go b/nodata.go new file mode 100644 index 0000000..b30a239 --- /dev/null +++ b/nodata.go @@ -0,0 +1,75 @@ +package main + +import ( + // External + werr "git.gibonuddevalla.se/go/wrappederror" + + // Standard + "database/sql" + "time" +) + +// nodataLoop checks if datapoint last_value is larger than the nodata_problem_seconds period and +// marks them as problems. They are then notified. +func nodataLoop() { + var ids []int + var err error + + // TODO - should be configurable + ticker := time.NewTicker(time.Second * 5) + for { + <-ticker.C + ids, err = nodataDatapointIDs() + if err != nil { + err = werr.Wrap(err).Log() + logger.Error("nodata", "error", err) + continue + } + + if len(ids) == 0 { + continue + } + + logger.Info("nodata", "problem_ids", ids) + } +} + +func nodataDatapointIDs() (ids []int, err error) { + ids = []int{} + + var rows *sql.Rows + rows, err = service.Db.Conn.Query(` + UPDATE datapoint + SET + nodata_is_problem = true + FROM ( + SELECT + id + FROM + datapoint + WHERE + NOT nodata_is_problem AND + extract(EPOCH from (NOW() - last_value))::int > nodata_problem_seconds + ) AS subquery + WHERE + datapoint.id = subquery.id + RETURNING + datapoint.id + + `) + if err != nil { + err = werr.Wrap(err) + return + } + defer rows.Close() + + var id int + for rows.Next() { + if err = rows.Scan(&id); err != nil { + err = werr.Wrap(err) + return + } + ids = append(ids, id) + } + return +} diff --git a/sql/00014.sql b/sql/00014.sql new file mode 100644 index 0000000..e31a5d5 --- /dev/null +++ b/sql/00014.sql @@ -0,0 +1,4 @@ +ALTER TABLE datapoint ADD COLUMN nodata_problem_seconds INT4 NOT NULL DEFAULT 0; +ALTER TABLE datapoint ADD COLUMN nodata_is_problem BOOL NOT NULL DEFAULT false; + +CREATE INDEX datapoint_last_value_idx ON public.datapoint ("last_value"); diff --git a/views/pages/datapoint_edit.gotmpl b/views/pages/datapoint_edit.gotmpl index 9e9ee66..9d65b50 100644 --- a/views/pages/datapoint_edit.gotmpl +++ b/views/pages/datapoint_edit.gotmpl @@ -25,6 +25,13 @@
+
No data
problem time
(seconds)
+
+ +
A problem is raised and notified if an entry isn't made within this time.
+
Set to 0 to disable.
+
+
{{ if eq .Data.Datapoint.ID 0 }} From fe9571c4665231eca8082a72d4f54b6a6b506e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 25 May 2024 15:00:01 +0200 Subject: [PATCH 027/152] nodata ui changes --- go.mod | 4 ++-- go.sum | 4 ++-- static/css/datapoints.css | 26 ++++++++++++++------------ static/css/main.css | 25 +++++++++++++------------ static/css/problems.css | 25 +++++++++++++------------ static/css/theme.css | 25 +++++++++++++------------ static/css/trigger_edit.css | 25 +++++++++++++------------ static/js/datapoint_edit.mjs | 2 +- static/less/datapoints.less | 1 + static/less/theme.less | 28 ++++++++++++++-------------- 10 files changed, 86 insertions(+), 79 deletions(-) diff --git a/go.mod b/go.mod index d8dc4da..32bf233 100644 --- a/go.mod +++ b/go.mod @@ -6,14 +6,14 @@ require ( git.gibonuddevalla.se/go/webservice v0.2.15 git.gibonuddevalla.se/go/wrappederror v0.3.4 github.com/expr-lang/expr v1.16.5 + github.com/jmoiron/sqlx v1.3.5 + github.com/lib/pq v1.10.9 ) require ( git.gibonuddevalla.se/go/dbschema v1.3.0 // indirect github.com/google/uuid v1.5.0 // indirect github.com/gorilla/websocket v1.5.1 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect - github.com/lib/pq v1.10.9 // indirect golang.org/x/net v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 26c1b8f..03a3465 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ git.gibonuddevalla.se/go/dbschema v1.3.0 h1:HzFMR29tWfy/ibIjltTbIMI4inVktj/rh8bESALibgM= git.gibonuddevalla.se/go/dbschema v1.3.0/go.mod h1:BNw3q/574nXbGoeWyK+tLhRfggVkw2j2aXZzrBKC3ig= -git.gibonuddevalla.se/go/webservice v0.2.12 h1:IcaIycmF7eO88RmFQkslHaKRWYxXdciVQXUAvJ36b4g= -git.gibonuddevalla.se/go/webservice v0.2.12/go.mod h1:3uBS6nLbK9qbuGzDls8MZD5Xr9ORY1Srbj6v06BIhws= +git.gibonuddevalla.se/go/webservice v0.2.15 h1:ECe63fRDSrg3RJcgYV2pG+WsAQLVG8wvfHennz7aHsY= +git.gibonuddevalla.se/go/webservice v0.2.15/go.mod h1:3uBS6nLbK9qbuGzDls8MZD5Xr9ORY1Srbj6v06BIhws= git.gibonuddevalla.se/go/wrappederror v0.3.4 h1:dcKp9/+QrZSO3S4fVnq7yG2p7DUZVmlztBAb/OzoZNY= git.gibonuddevalla.se/go/wrappederror v0.3.4/go.mod h1:j4w320Hk1wvhOPjUaK4GgLvmtnjUUM5yVu6JFO1OCSc= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/static/css/datapoints.css b/static/css/datapoints.css index 1963b13..8333e8e 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -19,7 +19,7 @@ body { } body { background: #282828; - font-family: "Roboto", sans-serif; + font-family: sans-serif; font-weight: 300; color: #d5c4a1; font-size: 11pt; @@ -48,25 +48,16 @@ a:hover { b { font-weight: 500; } -.roboto-light { - font-family: "Roboto", sans-serif; - font-weight: 300; - font-style: normal; -} -.roboto-medium { - font-family: "Roboto", sans-serif; - font-weight: 500; - font-style: normal; -} input[type="text"], textarea, select { - font-family: "Roboto Mono", monospace; + font-family: monospace; background: #202020; color: #d5c4a1; padding: 4px 8px; border: none; font-size: 1em; + line-height: 1.5em; } button { background: #202020; @@ -97,6 +88,16 @@ span.seconds { 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; +} #datapoints { display: grid; grid-template-columns: repeat(5, min-content); @@ -137,6 +138,7 @@ label { } .widgets .label { margin-top: 4px; + white-space: nowrap; } .widgets input[type="text"], .widgets textarea { diff --git a/static/css/main.css b/static/css/main.css index e24674f..c2dc070 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -19,7 +19,7 @@ body { } body { background: #282828; - font-family: "Roboto", sans-serif; + font-family: sans-serif; font-weight: 300; color: #d5c4a1; font-size: 11pt; @@ -48,25 +48,16 @@ a:hover { b { font-weight: 500; } -.roboto-light { - font-family: "Roboto", sans-serif; - font-weight: 300; - font-style: normal; -} -.roboto-medium { - font-family: "Roboto", sans-serif; - font-weight: 500; - font-style: normal; -} input[type="text"], textarea, select { - font-family: "Roboto Mono", monospace; + font-family: monospace; background: #202020; color: #d5c4a1; padding: 4px 8px; border: none; font-size: 1em; + line-height: 1.5em; } button { background: #202020; @@ -97,6 +88,16 @@ span.seconds { 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; +} #layout { display: grid; grid-template-areas: "menu content"; diff --git a/static/css/problems.css b/static/css/problems.css index 5b6316c..080965f 100644 --- a/static/css/problems.css +++ b/static/css/problems.css @@ -19,7 +19,7 @@ body { } body { background: #282828; - font-family: "Roboto", sans-serif; + font-family: sans-serif; font-weight: 300; color: #d5c4a1; font-size: 11pt; @@ -48,25 +48,16 @@ a:hover { b { font-weight: 500; } -.roboto-light { - font-family: "Roboto", sans-serif; - font-weight: 300; - font-style: normal; -} -.roboto-medium { - font-family: "Roboto", sans-serif; - font-weight: 500; - font-style: normal; -} input[type="text"], textarea, select { - font-family: "Roboto Mono", monospace; + font-family: monospace; background: #202020; color: #d5c4a1; padding: 4px 8px; border: none; font-size: 1em; + line-height: 1.5em; } button { background: #202020; @@ -97,6 +88,16 @@ span.seconds { 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; +} #problems-list, #acknowledged-list { display: grid; diff --git a/static/css/theme.css b/static/css/theme.css index 68c59d9..104cf3d 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -19,7 +19,7 @@ body { } body { background: #282828; - font-family: "Roboto", sans-serif; + font-family: sans-serif; font-weight: 300; color: #d5c4a1; font-size: 11pt; @@ -48,25 +48,16 @@ a:hover { b { font-weight: 500; } -.roboto-light { - font-family: "Roboto", sans-serif; - font-weight: 300; - font-style: normal; -} -.roboto-medium { - font-family: "Roboto", sans-serif; - font-weight: 500; - font-style: normal; -} input[type="text"], textarea, select { - font-family: "Roboto Mono", monospace; + font-family: monospace; background: #202020; color: #d5c4a1; padding: 4px 8px; border: none; font-size: 1em; + line-height: 1.5em; } button { background: #202020; @@ -97,3 +88,13 @@ span.seconds { 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; +} diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index 477f462..b2600b6 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -19,7 +19,7 @@ body { } body { background: #282828; - font-family: "Roboto", sans-serif; + font-family: sans-serif; font-weight: 300; color: #d5c4a1; font-size: 11pt; @@ -48,25 +48,16 @@ a:hover { b { font-weight: 500; } -.roboto-light { - font-family: "Roboto", sans-serif; - font-weight: 300; - font-style: normal; -} -.roboto-medium { - font-family: "Roboto", sans-serif; - font-weight: 500; - font-style: normal; -} input[type="text"], textarea, select { - font-family: "Roboto Mono", monospace; + font-family: monospace; background: #202020; color: #d5c4a1; padding: 4px 8px; border: none; font-size: 1em; + line-height: 1.5em; } button { background: #202020; @@ -97,6 +88,16 @@ span.seconds { 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; +} .widgets { display: grid; grid-template-columns: min-content 1fr; diff --git a/static/js/datapoint_edit.mjs b/static/js/datapoint_edit.mjs index 611011c..dfe5e9c 100644 --- a/static/js/datapoint_edit.mjs +++ b/static/js/datapoint_edit.mjs @@ -1,7 +1,7 @@ export class UI { constructor() { document.addEventListener('keydown', evt=>this.keyHandler(evt)) - document.querySelector('input[name="name"]').focus() + document.querySelector('input[name="group"]').focus() } keyHandler(evt) { if (!(evt.altKey && evt.shiftKey)) diff --git a/static/less/datapoints.less b/static/less/datapoints.less index 26ee63d..770db5f 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -47,6 +47,7 @@ .label { margin-top: 4px; + white-space: nowrap; } input[type="text"], textarea { diff --git a/static/less/theme.less b/static/less/theme.less index 513db5f..11ae2fc 100644 --- a/static/less/theme.less +++ b/static/less/theme.less @@ -44,7 +44,7 @@ body { body { background: @bg1; - font-family: "Roboto", sans-serif; + font-family: sans-serif; font-weight: 300; color: @text1; font-size: 11pt; @@ -80,27 +80,16 @@ b { font-weight: @bold; } -.roboto-light { - font-family: "Roboto", sans-serif; - font-weight: 300; - font-style: normal; -} - -.roboto-medium { - font-family: "Roboto", sans-serif; - font-weight: 500; - font-style: normal; -} - input[type="text"], textarea, select { - font-family: "Roboto Mono", monospace; + font-family: monospace; background: @bg2; color: @text1; padding: 4px 8px; border: none; font-size: 1em; + line-height: 1.5em; // fix for chrome hiding underscores } button { @@ -138,3 +127,14 @@ span.seconds { label { user-select: none; } + +.description { + border: 1px solid .lighterOrDarker(@bg3, 25%)[@result]; + color: @color4; + background: @bg2; + padding: 4px 8px; + margin-top: 8px; + white-space: nowrap; + width: min-content; + border-radius: 8px; +} From f77a65ff662d3f1a938c55a4e21e4c6d385d135f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 27 May 2024 20:03:25 +0200 Subject: [PATCH 028/152] Added problem state in notification --- main.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/main.go b/main.go index 883457f..ab88c7c 100644 --- a/main.go +++ b/main.go @@ -203,7 +203,7 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { / logger.Error("entry", "error", err) } - // Multiple triggers can use the same datapoint. + // Multiple triggers can use the same datapoint. for _, trigger := range triggers { var out any out, err = trigger.Run() @@ -215,25 +215,25 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { / logger.Debug("entry", "datapoint", dpoint, "value", value, "trigger", trigger, "result", out) var problemID int + var problemState string switch v := out.(type) { case bool: // Trigger returning true - a problem occurred if v { problemID, err = ProblemStart(trigger) - logger.Info("FOO", "problemID", problemID, "err==nil", err == nil) if err != nil { err = werr.Wrap(err).Log() logger.Error("entry", "error", err) } - + problemState = "PROBLEM" } else { // A problem didn't occur. problemID, err = ProblemClose(trigger) - logger.Info("FOO", "problemID", problemID, "err==nil", err == nil) if err != nil { err = werr.Wrap(err).Log() logger.Error("entry", "error", err) } + problemState = "OK" } // Has a change in problem state happened? @@ -244,7 +244,7 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { / logger.Debug("notification", "trigger", trigger.ID, "state", "change") } - err = notificationManager.Send(problemID, []byte(trigger.Name), func(notificationService *notification.Service, err error) { + err = notificationManager.Send(problemID, []byte(problemState+": "+trigger.Name), func(notificationService *notification.Service, err error) { logger.Info( "notification", "service", (*notificationService).GetType(), @@ -611,12 +611,12 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) { PAGE: "datapoint_values", MENU: "datapoints", Icon: "datapoints", - Label: "Values for "+datapoint.Name, + Label: "Values for " + datapoint.Name, } page.Data = map[string]any{ "Datapoint": datapoint, - "Values": values, + "Values": values, } page.Render(w) return From 3af2cd4d17d252b0ed7b8c565f0fb9ad62810db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 27 May 2024 23:01:34 +0200 Subject: [PATCH 029/152] Added nodata notification --- nodata.go | 78 ++++++++++++++++++++++++++++++++++++++++++++------- sql/00015.sql | 3 ++ 2 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 sql/00015.sql diff --git a/nodata.go b/nodata.go index b30a239..ba1e625 100644 --- a/nodata.go +++ b/nodata.go @@ -4,38 +4,95 @@ import ( // External werr "git.gibonuddevalla.se/go/wrappederror" + // Internal + "smon/notification" + // Standard "database/sql" + "encoding/json" "time" ) +var ( + nodataAreaID int + nodataSectionID int +) + // nodataLoop checks if datapoint last_value is larger than the nodata_problem_seconds period and // marks them as problems. They are then notified. func nodataLoop() { - var ids []int + var datapoints []Datapoint var err error // TODO - should be configurable ticker := time.NewTicker(time.Second * 5) for { <-ticker.C - ids, err = nodataDatapointIDs() + datapoints, err = nodataDatapoints() if err != nil { err = werr.Wrap(err).Log() logger.Error("nodata", "error", err) continue } - if len(ids) == 0 { + if len(datapoints) == 0 { continue } - logger.Info("nodata", "problem_ids", ids) + for _, datapoint := range datapoints { + nodataNotify(datapoint, "[NODATA] problem") + } + + logger.Info("nodata", "problem_ids", datapoints) } } -func nodataDatapointIDs() (ids []int, err error) { - ids = []int{} +func nodataNotify(datapoint Datapoint, state string) (err error) { + err = notificationManager.Send(-1, []byte(state+": "+datapoint.Name), func(notificationService *notification.Service, err error) { + logger.Info( + "notification", + "service", (*notificationService).GetType(), + "datapointID", datapoint.ID, + "prio", (*notificationService).GetPrio(), + "ok", true, + ) + + var errBody any + if err != nil { + errBody, _ = json.Marshal(err) + } else { + errBody = nil + } + _, err = service.Db.Conn.Exec( + ` + INSERT INTO notification_send(notification_id, datapoint_nodata_id, uuid, ok, error) + SELECT + id, $3, '', $4, $5 + FROM notification + WHERE + service=$1 AND + prio=$2 + `, + (*notificationService).GetType(), + (*notificationService).GetPrio(), + datapoint.ID, + err == nil, + errBody, + ) + if err != nil { + err = werr.Wrap(err).Log() + logger.Error("entry", "error", err) + } + }) + if err != nil { + err = werr.Wrap(err).Log() + logger.Error("notification", "error", err) + } + return +} + +func nodataDatapoints() (datapoints []Datapoint, err error) { + datapoints = []Datapoint{} var rows *sql.Rows rows, err = service.Db.Conn.Query(` @@ -54,7 +111,8 @@ func nodataDatapointIDs() (ids []int, err error) { WHERE datapoint.id = subquery.id RETURNING - datapoint.id + datapoint.id, + datapoint.name `) if err != nil { @@ -63,13 +121,13 @@ func nodataDatapointIDs() (ids []int, err error) { } defer rows.Close() - var id int + var dp Datapoint for rows.Next() { - if err = rows.Scan(&id); err != nil { + if err = rows.Scan(&dp.ID, &dp.Name); err != nil { err = werr.Wrap(err) return } - ids = append(ids, id) + datapoints = append(datapoints, dp) } return } diff --git a/sql/00015.sql b/sql/00015.sql new file mode 100644 index 0000000..0787d66 --- /dev/null +++ b/sql/00015.sql @@ -0,0 +1,3 @@ +ALTER TABLE public.notification_send ALTER COLUMN problem_id DROP NOT NULL; +ALTER TABLE public.notification_send ADD datapoint_nodata_id int4 NULL; +ALTER TABLE public.notification_send ADD CONSTRAINT notification_send_datapoint_fk FOREIGN KEY (datapoint_nodata_id) REFERENCES public.datapoint(id) ON DELETE CASCADE ON UPDATE CASCADE; From 2f95c732b6d41f05529a4d3c074fb7c5030eb13a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 06:57:07 +0200 Subject: [PATCH 030/152] Fixed nodata problem on datapoints not using it --- nodata.go | 1 + 1 file changed, 1 insertion(+) diff --git a/nodata.go b/nodata.go index ba1e625..a6cba54 100644 --- a/nodata.go +++ b/nodata.go @@ -105,6 +105,7 @@ func nodataDatapoints() (datapoints []Datapoint, err error) { FROM datapoint WHERE + nodata_problem_seconds > 0 AND NOT nodata_is_problem AND extract(EPOCH from (NOW() - last_value))::int > nodata_problem_seconds ) AS subquery From 67319defac128d0e3886055860f55e3bfb6ba7b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 07:23:27 +0200 Subject: [PATCH 031/152] Show NODATA problems in UI --- problem.go | 29 ++++++++++++--- static/css/problems.css | 1 + static/images/acknowledge.svg | 67 +++++++++++++++++++++++++++++++++++ static/less/problems.less | 1 + views/pages/problems.gotmpl | 45 +++++++++++++---------- 5 files changed, 120 insertions(+), 23 deletions(-) create mode 100644 static/images/acknowledge.svg diff --git a/problem.go b/problem.go index 1286828..68a8695 100644 --- a/problem.go +++ b/problem.go @@ -25,9 +25,9 @@ func ProblemsRetrieve() (problems []Problem, err error) { problems = []Problem{} row := service.Db.Conn.QueryRow(` SELECT - jsonb_agg(p.*) + jsonb_agg(problems.*) FROM ( - SELECT + (SELECT p.id, p.start, p.end, @@ -40,12 +40,31 @@ func ProblemsRetrieve() (problems []Problem, err error) { INNER JOIN "trigger" t ON p.trigger_id = t.id INNER JOIN section s ON t.section_id = s.id INNER JOIN area a ON s.area_id = a.id - WHERE p.end IS NULL + ORDER BY + p.start DESC) + + UNION ALL - ORDER BY p.start DESC - ) p + (SELECT + -1 AS id, + null, + null, + false, + -1 AS trigger_id, + CONCAT( + 'NODATA: ', + dp.name + ) AS trigger_name, + '[NODATA]' AS area_name, + '[NODATA]' AS section_name + FROM datapoint dp + WHERE + dp.nodata_is_problem + ORDER BY + dp.name ASC) + ) AS problems `) var jsonBody []byte diff --git a/static/css/problems.css b/static/css/problems.css index 080965f..4cb921b 100644 --- a/static/css/problems.css +++ b/static/css/problems.css @@ -108,6 +108,7 @@ label { #problems-list div, #acknowledged-list div { white-space: nowrap; + line-height: 24px; } #problems-list .header, #acknowledged-list .header { diff --git a/static/images/acknowledge.svg b/static/images/acknowledge.svg new file mode 100644 index 0000000..bec58fc --- /dev/null +++ b/static/images/acknowledge.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + thumb-up + + + diff --git a/static/less/problems.less b/static/less/problems.less index 6e6a395..c0dc6d9 100644 --- a/static/less/problems.less +++ b/static/less/problems.less @@ -8,6 +8,7 @@ div { white-space: nowrap; + line-height: 24px; } .header { diff --git a/views/pages/problems.gotmpl b/views/pages/problems.gotmpl index 27fe2ab..82df18b 100644 --- a/views/pages/problems.gotmpl +++ b/views/pages/problems.gotmpl @@ -19,15 +19,24 @@
Section
Since
{{ range .Data.Problems }} - {{ if .Acknowledged }} - {{ continue }} - {{ end }} -
- -
{{ .AreaName }}
-
{{ .SectionName }}
-
{{ format_time .Start }}
-
+ {{ if .Acknowledged }} + {{ continue }} + {{ end }} +
+ + {{ if eq .TriggerID -1 }} +
{{ .TriggerName }}
+
{{ .AreaName }}
+
{{ .SectionName }}
+
+
+ {{ else }} + +
{{ .AreaName }}
+
{{ .SectionName }}
+
{{ format_time .Start }}
+
+ {{ end }} {{ end }}
@@ -39,15 +48,15 @@
Since
{{ range .Data.Problems }} - {{ if not .Acknowledged }} - {{ continue }} - {{ end }} -
- -
{{ .AreaName }}
-
{{ .SectionName }}
-
{{ format_time .Start }}
-
+ {{ if not .Acknowledged }} + {{ continue }} + {{ end }} +
+ +
{{ .AreaName }}
+
{{ .SectionName }}
+
{{ format_time .Start }}
+
{{ end }}
From 8dd4fcd197db1de3f25f554a99171e548324fff4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 07:23:43 +0200 Subject: [PATCH 032/152] Bumped to v12 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index e40494a..e96246b 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" ) -const VERSION = "v11" +const VERSION = "v12" var ( logger *slog.Logger From c82c770246b654446bcd18a48671ef9ca094a1d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 07:37:22 +0200 Subject: [PATCH 033/152] UI fixes --- static/css/datapoints.css | 12 +++++++++--- static/css/main.css | 12 ++++++++++-- static/css/problems.css | 8 ++++++-- static/css/theme.css | 8 ++++++-- static/css/trigger_edit.css | 8 ++++++-- static/less/datapoints.less | 4 +++- static/less/main.less | 5 +++++ static/less/theme.less | 7 +++++-- views/pages/configuration.gotmpl | 2 +- views/pages/datapoints.gotmpl | 2 +- 10 files changed, 52 insertions(+), 16 deletions(-) diff --git a/static/css/datapoints.css b/static/css/datapoints.css index 8333e8e..54d331a 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -26,9 +26,12 @@ body { } h1, h2 { - margin-top: 0px; margin-bottom: 4px; } +h1:first-child, +h2:first-child { + margin-top: 0px; +} h1 { font-size: 1.5em; color: #fb4934; @@ -36,10 +39,11 @@ h1 { } h2 { font-size: 1.25em; + color: #b8bb26; font-weight: 500; } a { - color: #fabd2f; + color: #3f9da1; text-decoration: none; } a:hover { @@ -107,9 +111,11 @@ label { #datapoints .group { font-size: 1.1em; font-weight: bold; - color: #fabd2f; + color: #b8bb26; margin-top: 1.5em; padding-bottom: 4px; +} +#datapoints h2 { border-bottom: unset; } #datapoints .header { diff --git a/static/css/main.css b/static/css/main.css index c2dc070..67e2da9 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -26,9 +26,12 @@ body { } h1, h2 { - margin-top: 0px; margin-bottom: 4px; } +h1:first-child, +h2:first-child { + margin-top: 0px; +} h1 { font-size: 1.5em; color: #fb4934; @@ -36,10 +39,11 @@ h1 { } h2 { font-size: 1.25em; + color: #b8bb26; font-weight: 500; } a { - color: #fabd2f; + color: #3f9da1; text-decoration: none; } a:hover { @@ -178,6 +182,10 @@ 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/css/problems.css b/static/css/problems.css index 4cb921b..820b6e0 100644 --- a/static/css/problems.css +++ b/static/css/problems.css @@ -26,9 +26,12 @@ body { } h1, h2 { - margin-top: 0px; margin-bottom: 4px; } +h1:first-child, +h2:first-child { + margin-top: 0px; +} h1 { font-size: 1.5em; color: #fb4934; @@ -36,10 +39,11 @@ h1 { } h2 { font-size: 1.25em; + color: #b8bb26; font-weight: 500; } a { - color: #fabd2f; + color: #3f9da1; text-decoration: none; } a:hover { diff --git a/static/css/theme.css b/static/css/theme.css index 104cf3d..215acbb 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -26,9 +26,12 @@ body { } h1, h2 { - margin-top: 0px; margin-bottom: 4px; } +h1:first-child, +h2:first-child { + margin-top: 0px; +} h1 { font-size: 1.5em; color: #fb4934; @@ -36,10 +39,11 @@ h1 { } h2 { font-size: 1.25em; + color: #b8bb26; font-weight: 500; } a { - color: #fabd2f; + color: #3f9da1; text-decoration: none; } a:hover { diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index b2600b6..71cbb6a 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -26,9 +26,12 @@ body { } h1, h2 { - margin-top: 0px; margin-bottom: 4px; } +h1:first-child, +h2:first-child { + margin-top: 0px; +} h1 { font-size: 1.5em; color: #fb4934; @@ -36,10 +39,11 @@ h1 { } h2 { font-size: 1.25em; + color: #b8bb26; font-weight: 500; } a { - color: #fabd2f; + color: #3f9da1; text-decoration: none; } a:hover { diff --git a/static/less/datapoints.less b/static/less/datapoints.less index 770db5f..04086c7 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -9,9 +9,11 @@ .group { font-size: 1.10em; font-weight: bold; - color: @color2; + color: @color3; margin-top: 1.5em; padding-bottom: 4px; + } + h2 { border-bottom: unset; } diff --git a/static/less/main.less b/static/less/main.less index baf874c..bd42096 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -94,6 +94,11 @@ margin-top: 12px; margin-bottom: 20px; + &.configuration { + margin-top: 8px; + margin-bottom: 8px; + } + &:last-child { margin-bottom: 12px; } diff --git a/static/less/theme.less b/static/less/theme.less index 11ae2fc..93872ea 100644 --- a/static/less/theme.less +++ b/static/less/theme.less @@ -52,8 +52,10 @@ body { h1, h2 { - margin-top: 0px; margin-bottom: 4px; + &:first-child { + margin-top: 0px; + } } h1 { @@ -64,11 +66,12 @@ h1 { h2 { font-size: 1.25em; + color: @color3; font-weight: @bold; } a { - color: @color2; + color: @color4; text-decoration: none; &:hover { diff --git a/views/pages/configuration.gotmpl b/views/pages/configuration.gotmpl index d65b27d..0fdfd54 100644 --- a/views/pages/configuration.gotmpl +++ b/views/pages/configuration.gotmpl @@ -64,7 +64,7 @@ Create
{{ range .SortedSections }} -
+
{{ .Name }}
{{ end }} diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl index 91fc6c1..4f4340d 100644 --- a/views/pages/datapoints.gotmpl +++ b/views/pages/datapoints.gotmpl @@ -10,7 +10,7 @@ {{ $prevGroup := "kalle" }} {{ range .Data.Datapoints }} {{ if ne $prevGroup .Group }} -
{{ .Group }}
+

{{ .Group }}

Name
Datatype
Last value
From 0a7130f3158905b3fdc6d24088f3c12f8ca14014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 12:03:28 +0200 Subject: [PATCH 034/152] Graph for front page --- static/css/index.css | 109 +++++++++++++++++++++++++++++++++++++ static/images/graph.drawio | 74 +++++++++++++++++++++++++ static/images/graph.svg | 4 ++ static/less/index.less | 7 +++ views/pages/index.gotmpl | 5 ++ 5 files changed, 199 insertions(+) create mode 100644 static/css/index.css create mode 100644 static/images/graph.drawio create mode 100644 static/images/graph.svg create mode 100644 static/less/index.less diff --git a/static/css/index.css b/static/css/index.css new file mode 100644 index 0000000..1cb2413 --- /dev/null +++ b/static/css/index.css @@ -0,0 +1,109 @@ +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: 500; +} +h2 { + font-size: 1.25em; + color: #b8bb26; + font-weight: 500; +} +a { + color: #3f9da1; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +b { + font-weight: 500; +} +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: 500; +} +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; +} +.graph { + margin-top: 32px; + padding: 32px; + border-radius: 16px; +} diff --git a/static/images/graph.drawio b/static/images/graph.drawio new file mode 100644 index 0000000..1f93f5a --- /dev/null +++ b/static/images/graph.drawio @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/static/images/graph.svg b/static/images/graph.svg new file mode 100644 index 0000000..8915efe --- /dev/null +++ b/static/images/graph.svg @@ -0,0 +1,4 @@ + + + +
Datapoint
/entry/<name>
Datapoint...
Problem
is raised
Problem...
 true 
 true 
 false 
 false 
Trigger
is evaluated
Trigger...
 Data 
 Data 
Notification
is sent
Notification...
Datapoint
/entry/<name>
Datapoint...
 Data 
 Data 
Problem
is resolved
Problem...
Text is not SVG - cannot display
\ No newline at end of file diff --git a/static/less/index.less b/static/less/index.less new file mode 100644 index 0000000..7c65786 --- /dev/null +++ b/static/less/index.less @@ -0,0 +1,7 @@ +@import "theme.less"; + +.graph { + margin-top: 32px; + padding: 32px; + border-radius: 16px; +} diff --git a/views/pages/index.gotmpl b/views/pages/index.gotmpl index 7e6e275..413e8b8 100644 --- a/views/pages/index.gotmpl +++ b/views/pages/index.gotmpl @@ -1,4 +1,5 @@ {{ define "page" }} +
@@ -7,6 +8,10 @@

SMon

{{ .VERSION }}

+ +
+ +
{{ end }} From db21b01589562d7250386980b18195adf3ab6209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 12:03:40 +0200 Subject: [PATCH 035/152] Bumped to v13 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index e96246b..1dcfe3f 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" ) -const VERSION = "v12" +const VERSION = "v13" var ( logger *slog.Logger From e2f888f160d811b97fb51a06cd4a0a7af05d7479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 12:56:35 +0200 Subject: [PATCH 036/152] Areas/section view for problems --- main.go | 19 +++++- static/css/datapoints.css | 10 ++-- static/css/index.css | 8 +-- static/css/main.css | 16 ++--- static/css/problems.css | 35 +++++++++-- static/css/theme.css | 8 +-- static/css/trigger_edit.css | 8 +-- static/js/problems.mjs | 9 +++ static/less/main.less | 2 +- static/less/problems.less | 30 ++++++++++ static/less/theme.less | 2 +- views/pages/index.gotmpl | 2 +- views/pages/problems.gotmpl | 116 +++++++++++++++++++++--------------- 13 files changed, 182 insertions(+), 83 deletions(-) diff --git a/main.go b/main.go index 1dcfe3f..fa10e36 100644 --- a/main.go +++ b/main.go @@ -443,8 +443,25 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ return } + var found bool + problemsGrouped := make(map[string]map[string][]Problem) + for _, problem := range problems { + if _, found = problemsGrouped[problem.AreaName]; !found { + problemsGrouped[problem.AreaName] = make(map[string][]Problem) + } + if _, found = problemsGrouped[problem.AreaName][problem.SectionName]; !found { + problemsGrouped[problem.AreaName][problem.SectionName] = []Problem{} + } + + problemsGrouped[problem.AreaName][problem.SectionName] = append( + problemsGrouped[problem.AreaName][problem.SectionName], + problem, + ) + } + page.Data = map[string]any{ - "Problems": problems, + "Problems": problems, + "ProblemsGrouped": problemsGrouped, } page.Render(w) return diff --git a/static/css/datapoints.css b/static/css/datapoints.css index 54d331a..f882929 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -35,12 +35,12 @@ h2:first-child { h1 { font-size: 1.5em; color: #fb4934; - font-weight: 500; + font-weight: 800; } h2 { font-size: 1.25em; color: #b8bb26; - font-weight: 500; + font-weight: 800; } a { color: #3f9da1; @@ -50,7 +50,7 @@ a:hover { text-decoration: underline; } b { - font-weight: 500; + font-weight: 800; } input[type="text"], textarea, @@ -80,7 +80,7 @@ button:focus { } span.date { color: #d5c4a1; - font-weight: 500; + font-weight: 800; } span.time { font-size: 0.9em; @@ -119,7 +119,7 @@ label { border-bottom: unset; } #datapoints .header { - font-weight: 500; + font-weight: 800; font-size: 0.85em; color: #d5c4a1; } diff --git a/static/css/index.css b/static/css/index.css index 1cb2413..97b1882 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -35,12 +35,12 @@ h2:first-child { h1 { font-size: 1.5em; color: #fb4934; - font-weight: 500; + font-weight: 800; } h2 { font-size: 1.25em; color: #b8bb26; - font-weight: 500; + font-weight: 800; } a { color: #3f9da1; @@ -50,7 +50,7 @@ a:hover { text-decoration: underline; } b { - font-weight: 500; + font-weight: 800; } input[type="text"], textarea, @@ -80,7 +80,7 @@ button:focus { } span.date { color: #d5c4a1; - font-weight: 500; + font-weight: 800; } span.time { font-size: 0.9em; diff --git a/static/css/main.css b/static/css/main.css index 67e2da9..eda60f3 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -35,12 +35,12 @@ h2:first-child { h1 { font-size: 1.5em; color: #fb4934; - font-weight: 500; + font-weight: 800; } h2 { font-size: 1.25em; color: #b8bb26; - font-weight: 500; + font-weight: 800; } a { color: #3f9da1; @@ -50,7 +50,7 @@ a:hover { text-decoration: underline; } b { - font-weight: 500; + font-weight: 800; } input[type="text"], textarea, @@ -80,7 +80,7 @@ button:focus { } span.date { color: #d5c4a1; - font-weight: 500; + font-weight: 800; } span.time { font-size: 0.9em; @@ -152,7 +152,7 @@ label { margin-bottom: 32px; } #page .page-label div { - font-weight: 500; + font-weight: 800; font-size: 1.5em; color: #fb4934; } @@ -172,7 +172,7 @@ label { #areas .area > .name { background: #fb4934; color: #fff; - font-weight: 500; + font-weight: 800; padding: 4px 16px; border-top-left-radius: 4px; border-top-right-radius: 4px; @@ -196,10 +196,10 @@ label { white-space: nowrap; } #areas .area .section .create .new { - font-weight: 500; + font-weight: 800; } #areas .area .section > .name { - font-weight: 500; + font-weight: 800; } #areas .area .section .triggers .trigger { display: grid; diff --git a/static/css/problems.css b/static/css/problems.css index 820b6e0..afbf302 100644 --- a/static/css/problems.css +++ b/static/css/problems.css @@ -35,12 +35,12 @@ h2:first-child { h1 { font-size: 1.5em; color: #fb4934; - font-weight: 500; + font-weight: 800; } h2 { font-size: 1.25em; color: #b8bb26; - font-weight: 500; + font-weight: 800; } a { color: #3f9da1; @@ -50,7 +50,7 @@ a:hover { text-decoration: underline; } b { - font-weight: 500; + font-weight: 800; } input[type="text"], textarea, @@ -80,7 +80,7 @@ button:focus { } span.date { color: #d5c4a1; - font-weight: 500; + font-weight: 800; } span.time { font-size: 0.9em; @@ -116,12 +116,12 @@ label { } #problems-list .header, #acknowledged-list .header { - font-weight: 500; + font-weight: 800; } #problems-list .trigger, #acknowledged-list .trigger { color: #fb4934; - font-weight: 500; + font-weight: 800; } #problems-list .acknowledge img, #acknowledged-list .acknowledge img { @@ -130,3 +130,26 @@ label { #acknowledged-list.hidden { display: none; } +#areas { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 16px; +} +#areas .area .section { + display: grid; + grid-template-columns: repeat(2, min-content); + grid-gap: 8px 12px; +} +#areas .area .section .name { + color: #f7edd7; + grid-column: 1 / -1; + font-weight: bold !important; + line-height: 24px; +} +#areas .area .section div { + white-space: nowrap; +} +.hidden { + display: none; +} diff --git a/static/css/theme.css b/static/css/theme.css index 215acbb..98f3fa0 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -35,12 +35,12 @@ h2:first-child { h1 { font-size: 1.5em; color: #fb4934; - font-weight: 500; + font-weight: 800; } h2 { font-size: 1.25em; color: #b8bb26; - font-weight: 500; + font-weight: 800; } a { color: #3f9da1; @@ -50,7 +50,7 @@ a:hover { text-decoration: underline; } b { - font-weight: 500; + font-weight: 800; } input[type="text"], textarea, @@ -80,7 +80,7 @@ button:focus { } span.date { color: #d5c4a1; - font-weight: 500; + font-weight: 800; } span.time { font-size: 0.9em; diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index 71cbb6a..070964d 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -35,12 +35,12 @@ h2:first-child { h1 { font-size: 1.5em; color: #fb4934; - font-weight: 500; + font-weight: 800; } h2 { font-size: 1.25em; color: #b8bb26; - font-weight: 500; + font-weight: 800; } a { color: #3f9da1; @@ -50,7 +50,7 @@ a:hover { text-decoration: underline; } b { - font-weight: 500; + font-weight: 800; } input[type="text"], textarea, @@ -80,7 +80,7 @@ button:focus { } span.date { color: #d5c4a1; - font-weight: 500; + font-weight: 800; } span.time { font-size: 0.9em; diff --git a/static/js/problems.mjs b/static/js/problems.mjs index 54c4e3c..e3e2587 100644 --- a/static/js/problems.mjs +++ b/static/js/problems.mjs @@ -20,4 +20,13 @@ export class UI { localStorage.setItem('show_acknowledged', false) } } + + displayList() { + document.querySelector('.display-list').classList.remove('hidden') + document.querySelector('.display-areas').classList.add('hidden') + } + displayAreas() { + document.querySelector('.display-list').classList.add('hidden') + document.querySelector('.display-areas').classList.remove('hidden') + } } diff --git a/static/less/main.less b/static/less/main.less index bd42096..0f061e2 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -83,7 +83,7 @@ &>.name { background: @color1; color: #fff; - font-weight: 500; + font-weight: @bold; padding: 4px 16px; border-top-left-radius: 4px; border-top-right-radius: 4px; diff --git a/static/less/problems.less b/static/less/problems.less index c0dc6d9..a6b6551 100644 --- a/static/less/problems.less +++ b/static/less/problems.less @@ -30,3 +30,33 @@ #acknowledged-list.hidden{ display: none; } + +#areas { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 16px; + + .area { + .section { + display: grid; + grid-template-columns: repeat(2, min-content); + grid-gap: 8px 12px; + + .name { + color: @text2; + grid-column: 1 / -1; + font-weight: bold !important; + line-height: 24px; + } + + div { + white-space: nowrap; + } + } + } +} + +.hidden { + display: none; +} diff --git a/static/less/theme.less b/static/less/theme.less index 93872ea..9a27b23 100644 --- a/static/less/theme.less +++ b/static/less/theme.less @@ -12,7 +12,7 @@ @color4: #3f9da1; @color5: #fe8019; -@bold: 500; +@bold: 800; .lighterOrDarker(@color, @amount) { @result: lighten(@color, @amount); diff --git a/views/pages/index.gotmpl b/views/pages/index.gotmpl index 413e8b8..718cc72 100644 --- a/views/pages/index.gotmpl +++ b/views/pages/index.gotmpl @@ -10,7 +10,7 @@

{{ .VERSION }}

- +
diff --git a/views/pages/problems.gotmpl b/views/pages/problems.gotmpl index 82df18b..d729b8d 100644 --- a/views/pages/problems.gotmpl +++ b/views/pages/problems.gotmpl @@ -9,68 +9,88 @@ {{ block "page_label" . }}{{end}} - +
+ + +
-
-

Current

- -
Trigger
-
Area
-
Section
-
Since
- {{ range .Data.Problems }} - {{ if .Acknowledged }} - {{ continue }} + - +
+
+ {{ range $areaName, $sections := .Data.ProblemsGrouped }} +
+
{{ $areaName }}
-
- {{ range .Data.Areas }} -
-
{{ .Name }}
- {{ range .SortedSections }} -
-
{{ .Name }}
+ {{ range $sectionName, $problems := $sections }} +
+
{{ $sectionName }}
+ + {{ range $problems }} +
{{ .TriggerName }}
+ + {{ if eq (.Start | html) "0001-01-01 00:00:00 +0000 UTC" }} +
+ {{ else }} +
{{ format_time .Start }}
+ {{ end }} + {{ end }} +
+ {{ end }}
{{ end }}
- {{ end }}
{{ end }} From aa9376ac134711b9326611d0e90db647e028f10d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 28 May 2024 12:56:49 +0200 Subject: [PATCH 037/152] Bumped to v14 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index fa10e36..859e59f 100644 --- a/main.go +++ b/main.go @@ -26,7 +26,7 @@ import ( "time" ) -const VERSION = "v13" +const VERSION = "v14" var ( logger *slog.Logger From a94559ab6cdd39ac4572170cb27e04bf0176e897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 13:01:17 +0200 Subject: [PATCH 038/152] Added nodata_problem_seconds to datapoint view --- datapoint.go | 13 ++++++++----- static/css/datapoints.css | 2 +- static/less/datapoints.less | 2 +- views/pages/datapoints.gotmpl | 2 ++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/datapoint.go b/datapoint.go index f851a5b..ff284a2 100644 --- a/datapoint.go +++ b/datapoint.go @@ -161,6 +161,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.datatype, dp.last_value, dp.group, + dp.nodata_problem_seconds, dpv.id AS v_id, dpv.ts, @@ -185,11 +186,12 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ defer rows.Close() type DbRes struct { - ID int - Group string - Name string - Datatype DatapointType - LastValue time.Time `db:"last_value"` + ID int + Group string + Name string + Datatype DatapointType + LastValue time.Time `db:"last_value"` + NodataProblemSeconds int `db:"nodata_problem_seconds"` VID sql.NullInt64 `db:"v_id"` Ts sql.NullTime @@ -214,6 +216,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ dp.Datatype = res.Datatype dp.LastValue = res.LastValue dp.Found = true + dp.NodataProblemSeconds = res.NodataProblemSeconds if res.VID.Valid { dpv.ID = int(res.VID.Int64) diff --git a/static/css/datapoints.css b/static/css/datapoints.css index f882929..0057c02 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -104,7 +104,7 @@ label { } #datapoints { display: grid; - grid-template-columns: repeat(5, min-content); + grid-template-columns: repeat(6, min-content); grid-gap: 8px 16px; margin-top: 16px; } diff --git a/static/less/datapoints.less b/static/less/datapoints.less index 04086c7..b8ce8c5 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -2,7 +2,7 @@ #datapoints { display: grid; - grid-template-columns: repeat(5, min-content); + grid-template-columns: repeat(6, min-content); grid-gap: 8px 16px; margin-top: 16px; diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl index 4f4340d..1300e55 100644 --- a/views/pages/datapoints.gotmpl +++ b/views/pages/datapoints.gotmpl @@ -13,6 +13,7 @@

{{ .Group }}

Name
Datatype
+
No data
Last value
Value
@@ -22,6 +23,7 @@ {{ end }}
{{ .Datatype }}
+
{{ .NodataProblemSeconds }}
{{ format_time .LastValue }}
{{ if eq .Datatype "DATETIME" }}
{{ if .LastDatapointValue.ValueDateTime.Valid }}{{ format_time .LastDatapointValue.Value }}{{ end }}
From c45724f5d8ddd519796e68235488f30da89a4bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 13:05:42 +0200 Subject: [PATCH 039/152] Fixed bug in creating new datatype --- datapoint.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/datapoint.go b/datapoint.go index ff284a2..afb815e 100644 --- a/datapoint.go +++ b/datapoint.go @@ -73,7 +73,7 @@ func (dp Datapoint) Update() (err error) { // {{{ if dp.ID == 0 { _, err = service.Db.Conn.Exec( - `INSERT INTO datapoint("group", name, datatype) VALUES($1, $2, $3, $4)`, + `INSERT INTO datapoint("group", name, datatype, nodata_problem_seconds) VALUES($1, $2, $3, $4)`, dp.Group, name, dp.Datatype, From 9eecf946f8dd6a68b435c419f3282076853d9bcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 13:31:51 +0200 Subject: [PATCH 040/152] Configurable interval for nodata checks --- config.go | 3 ++- main.go | 6 +++++- nodata.go | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index 150fa1d..6ac26a3 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,6 @@ package main type SmonConfiguration struct { - LogFile string + LogFile string + NodataInterval int `json:"nodata_interval"` // in seconds } diff --git a/main.go b/main.go index 859e59f..085ea5c 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ var ( parsedTemplates map[string]*template.Template componentFilenames []string notificationManager notification.Manager + smonConf SmonConfiguration //go:embed sql sqlFS embed.FS @@ -88,7 +89,6 @@ func main() { // {{{ os.Exit(1) } - smonConf := SmonConfiguration{} j, _ := json.Marshal(service.Config.Application) json.Unmarshal(j, &smonConf) logFile, err = os.OpenFile(smonConf.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) @@ -96,6 +96,10 @@ func main() { // {{{ logger.Error("application", "error", err) return } + if smonConf.NodataInterval < 10 { + logger.Error("application → nodata_interval has to be larger or equal to 10.") + return + } service.SetDatabase(sqlProvider) service.SetStaticFS(staticFS, "static") diff --git a/nodata.go b/nodata.go index a6cba54..ea09de9 100644 --- a/nodata.go +++ b/nodata.go @@ -25,7 +25,7 @@ func nodataLoop() { var err error // TODO - should be configurable - ticker := time.NewTicker(time.Second * 5) + ticker := time.NewTicker(time.Second * time.Duration(smonConf.NodataInterval)) for { <-ticker.C datapoints, err = nodataDatapoints() From 68abb894a68542ca94dc41ac93070b7297d181ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 13:32:04 +0200 Subject: [PATCH 041/152] Don't erase datapoints when used in triggers. --- datapoint.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/datapoint.go b/datapoint.go index afb815e..02acec4 100644 --- a/datapoint.go +++ b/datapoint.go @@ -283,6 +283,37 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{ return } // }}} func DatapointDelete(id int) (err error) { // {{{ + var dpName string + row := service.Db.Conn.QueryRow(`SELECT name FROM public.datapoint WHERE id = $1`, id) + err = row.Scan(&dpName) + if err != nil { + err = werr.Wrap(err).WithData(id) + return + } + + var rows *sql.Rows + rows, err = service.Db.Conn.Query(`SELECT name FROM public.trigger WHERE datapoints ? $1`, dpName) + if err != nil { + err = werr.Wrap(err).WithData(dpName) + return + } + defer rows.Close() + + var triggerNames []string + var name string + for rows.Next() { + err = rows.Scan(&name) + if err != nil { + err = werr.Wrap(err) + return + } + triggerNames = append(triggerNames, name) + } + + if len(triggerNames) > 0 { + return werr.New("Datapoint '%s' used in the following triggers: %s", dpName, strings.Join(triggerNames, ", ")) + } + _, err = service.Db.Conn.Exec(`DELETE FROM datapoint WHERE id=$1`, id) if err != nil { err = werr.Wrap(err).WithData(id) From adab2ab67dc60429fe3fa4bdf44cccfe6e73a2c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 14:33:43 +0200 Subject: [PATCH 042/152] In-page addition of datapoints to triggers --- main.go | 34 ++++++++++++++++++++++++++++++++++ static/js/trigger_edit.mjs | 20 +++++++++++++++----- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/main.go b/main.go index 085ea5c..bb1df33 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "net/http" "os" "path" + "slices" "sort" "strconv" "time" @@ -138,6 +139,7 @@ func main() { // {{{ 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/addDatapoint/{id}/{datapointName}", false, false, pageTriggerDatapointAdd) service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate) service.Register("/trigger/run/{id}", false, false, pageTriggerRun) @@ -725,6 +727,38 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // page.Render(w) } // }}} +func pageTriggerDatapointAdd(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ + triggerID := r.PathValue("id") + dpName := r.PathValue("datapointName") + + id, err := strconv.Atoi(triggerID) + if err != nil { + httpError(w, werr.Wrap(err).Log()) + return + } + + var trigger Trigger + if id > 0 { + trigger, err = TriggerRetrieve(id) + if err != nil { + httpError(w, werr.Wrap(err).Log()) + return + } + } + + if !slices.Contains(trigger.Datapoints, dpName) { + trigger.Datapoints = append(trigger.Datapoints, dpName) + } + err = trigger.Update() + if err != nil { + httpError(w, werr.Wrap(err).Log()) + return + } + + j, _ := json.Marshal(struct{ OK bool }{OK: true}) + w.Header().Add("Content-Type", "application/json") + w.Write(j) +} // }}} func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) diff --git a/static/js/trigger_edit.mjs b/static/js/trigger_edit.mjs index d190ef1..ab90143 100644 --- a/static/js/trigger_edit.mjs +++ b/static/js/trigger_edit.mjs @@ -66,7 +66,7 @@ export class UI { }) .catch(err => alert(err)) }//}}} - chooseDatapoint() {//{{{ + async chooseDatapoint() {//{{{ const dlg = document.getElementById('dlg-datapoints') const datapoint = document.getElementById('datapoint').value const dp = this.datapoints.find(dp => dp.Name == datapoint) @@ -77,8 +77,10 @@ export class UI { } this.trigger.addDatapoint(dp) - dlg.close() - this.render() + .then(() => { + dlg.close() + this.render() + }) }//}}} deleteDatapoint(name) {//{{{ if (!confirm(`Delete ${name}?`)) { @@ -147,7 +149,15 @@ export class Trigger { }) .catch(err => alert(err)) }//}}} - addDatapoint(dp) {//{{{ - this.datapoints[dp.Name] = dp + async addDatapoint(dp) {//{{{ + return fetch(`/trigger/addDatapoint/${this.id}/${dp.Name}`) + .then(data => data.json()) + .then(json => { + if (!json.OK) { + alert(json.Error) + return + } + this.datapoints[dp.Name] = dp + }) }//}}} } From 4c622561e39903c3825a87b380e684b9670572f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 14:34:05 +0200 Subject: [PATCH 043/152] Bumped to v15 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index bb1df33..fd0ed62 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( "time" ) -const VERSION = "v14" +const VERSION = "v15" var ( logger *slog.Logger From 37bf6929841e3bb4e5be8b63b0345e44961d09c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 15:06:41 +0200 Subject: [PATCH 044/152] Better trigger create --- main.go | 33 +++++++++++++++++++++++++++++++++ trigger.go | 29 ++++++++++++++++++++++++++++- views/pages/triggers.gotmpl | 25 ++++++++++++++++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index fd0ed62..d8990d4 100644 --- a/main.go +++ b/main.go @@ -137,6 +137,7 @@ func main() { // {{{ 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/edit/{id}", false, false, pageTriggerEdit) service.Register("/trigger/edit/{id}/{sectionID}", false, false, pageTriggerEdit) service.Register("/trigger/addDatapoint/{id}/{datapointName}", false, false, pageTriggerDatapointAdd) @@ -672,6 +673,38 @@ func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page.Render(w) } // }}} +func triggerCreate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ + name := r.PathValue("name") + sectionIDStr := r.PathValue("sectionID") + sectionID, err := strconv.Atoi(sectionIDStr) + if err != nil { + httpError(w, werr.Wrap(err).WithData(sectionIDStr).Log()) + return + } + + t, err := TriggerCreate(sectionID, name) + if err != nil { + httpError(w, werr.Wrap(err).WithData(struct { + SectionID int + Name string + }{ + sectionID, + name, + }).Log()) + return + } + + resp := struct { + OK bool + Trigger Trigger + }{ + true, + t, + } + j, _ := json.Marshal(resp) + w.Header().Add("Content-Type", "application/json") + w.Write(j) +} // }}} func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ idStr := r.PathValue("id") id, err := strconv.Atoi(idStr) diff --git a/trigger.go b/trigger.go index 01d6043..6020f12 100644 --- a/trigger.go +++ b/trigger.go @@ -7,6 +7,7 @@ import ( "github.com/lib/pq" // Standard + "database/sql" "encoding/json" "fmt" "strings" @@ -20,6 +21,13 @@ type Trigger struct { Datapoints []string } +func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{ + t.SectionID = sectionID + t.Name = name + t.Expression = "false" + err = t.Update() + return +} // }}} func TriggersRetrieve() (areas []Area, err error) { // {{{ areas = []Area{} @@ -137,15 +145,34 @@ func (t *Trigger) Update() (err error) { // {{{ } jsonDatapoints, _ := json.Marshal(t.Datapoints) if t.ID == 0 { - _, err = service.Db.Conn.Exec(` + var row *sql.Row + row = service.Db.Conn.QueryRow(` INSERT INTO "trigger"(name, section_id, expression, datapoints) VALUES($1, $2, $3, $4) + RETURNING id `, t.Name, t.SectionID, t.Expression, jsonDatapoints, ) + err = row.Scan(&t.ID) + if err != nil { + err = we.Wrap(err).WithData( + struct { + SectionID int + Name string + Expression string + JsonDataPoints []byte + }{ + t.SectionID, + t.Name, + t.Expression, + jsonDatapoints, + }, + ) + return + } } else { _, err = service.Db.Conn.Exec(` UPDATE "trigger" diff --git a/views/pages/triggers.gotmpl b/views/pages/triggers.gotmpl index b934985..60baf48 100644 --- a/views/pages/triggers.gotmpl +++ b/views/pages/triggers.gotmpl @@ -3,6 +3,29 @@ {{ block "page_label" . }}{{end}} {{ $version := .VERSION }} + +
{{ range .Data.Areas }}
@@ -11,7 +34,7 @@
{{ .Name }}
- +
From a1201003055d9aeff30af3722caa45380ea5701c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 15:06:57 +0200 Subject: [PATCH 045/152] Bumped to v16 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index d8990d4..b8d04e0 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( "time" ) -const VERSION = "v15" +const VERSION = "v16" var ( logger *slog.Logger From c0238a2e5f9de01c1a885839d10cc11aae2017f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 30 May 2024 17:22:38 +0200 Subject: [PATCH 046/152] Updated config example --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 618fc5c..eaed26c 100644 --- a/README.md +++ b/README.md @@ -28,8 +28,9 @@ session: application: logfile: /var/log/smon.log + nodata_interval: 60 ``` # Data from systems to datapoints -`curl -d "$(time +'%F %T +02')" http://localhost:9000/entry/datapoint_name` \ No newline at end of file +`curl -d "$(time +'%F %T +02')" http://localhost:9000/entry/datapoint_name` From 9ceca8600f6e305d4cd4bfb79e1195df9fe57784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 1 Jun 2024 09:01:58 +0200 Subject: [PATCH 047/152] Index page UI adjustment --- static/css/index.css | 3 +-- static/less/index.less | 3 +-- views/pages/index.gotmpl | 6 +++--- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/static/css/index.css b/static/css/index.css index 97b1882..5c0077f 100644 --- a/static/css/index.css +++ b/static/css/index.css @@ -103,7 +103,6 @@ label { border-radius: 8px; } .graph { - margin-top: 32px; - padding: 32px; + margin-top: 192px; border-radius: 16px; } diff --git a/static/less/index.less b/static/less/index.less index 7c65786..97eb0bb 100644 --- a/static/less/index.less +++ b/static/less/index.less @@ -1,7 +1,6 @@ @import "theme.less"; .graph { - margin-top: 32px; - padding: 32px; + margin-top: 192px; border-radius: 16px; } diff --git a/views/pages/index.gotmpl b/views/pages/index.gotmpl index 718cc72..a922a56 100644 --- a/views/pages/index.gotmpl +++ b/views/pages/index.gotmpl @@ -9,9 +9,9 @@

SMon

{{ .VERSION }}

-
- -
+
+ +
{{ end }} From ef3f89cb2c2ad0425ba4117ad79ddeccd59a12c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 1 Jun 2024 09:18:56 +0200 Subject: [PATCH 048/152] Added trigger deletion --- main.go | 20 +++++- static/css/main.css | 13 ---- static/css/triggers.css | 117 ++++++++++++++++++++++++++++++++++++ static/less/main.less | 18 ------ static/less/triggers.less | 26 ++++++++ trigger.go | 27 ++++++--- views/pages/triggers.gotmpl | 18 ++++-- 7 files changed, 191 insertions(+), 48 deletions(-) create mode 100644 static/css/triggers.css create mode 100644 static/less/triggers.less diff --git a/main.go b/main.go index b8d04e0..90bf8b2 100644 --- a/main.go +++ b/main.go @@ -143,6 +143,7 @@ func main() { // {{{ 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/delete/{id}", false, false, actionTriggerDelete) service.Register("/configuration", false, false, pageConfiguration) service.Register("/entry/{datapoint}", false, false, entryDatapoint) @@ -695,7 +696,7 @@ func triggerCreate(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{ } resp := struct { - OK bool + OK bool Trigger Trigger }{ true, @@ -862,6 +863,23 @@ func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // { w.Header().Add("Content-Type", "application/json") w.Write(j) } // }}} +func actionTriggerDelete(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).Log()) + return + } + + err = TriggerDelete(id) + if err != nil { + httpError(w, werr.Wrap(err).Log()) + return + } + + w.Header().Add("Location", "/triggers") + w.WriteHeader(302) +} // }}} func pageConfiguration(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ areas, err := AreaRetrieve() diff --git a/static/css/main.css b/static/css/main.css index eda60f3..a213f0d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -201,19 +201,6 @@ label { #areas .area .section > .name { font-weight: 800; } -#areas .area .section .triggers .trigger { - display: grid; - grid-template-columns: min-content 1fr; - grid-gap: 8px; - align-items: center; - margin-top: 8px; -} -#areas .area .section .triggers .trigger img { - height: 16px; -} -#areas .area .section .triggers .trigger .label { - color: inherit; -} dialog { background: #202020; border: 1px solid #606060; diff --git a/static/css/triggers.css b/static/css/triggers.css new file mode 100644 index 0000000..352428d --- /dev/null +++ b/static/css/triggers.css @@ -0,0 +1,117 @@ +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 .section .triggers .trigger { + display: grid; + grid-template-columns: min-content 1fr min-content; + grid-gap: 8px; + align-items: center; + margin-top: 8px; +} +#areas .area .section .triggers .trigger img { + height: 16px; +} +#areas .area .section .triggers .trigger .label { + color: inherit; +} diff --git a/static/less/main.less b/static/less/main.less index 0f061e2..de80c87 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -117,24 +117,6 @@ &>.name { font-weight: @bold; } - - .triggers { - .trigger { - display: grid; - grid-template-columns: min-content 1fr; - grid-gap: 8px; - align-items: center; - margin-top: 8px; - - img { - height: 16px; - } - - .label { - color: inherit; - } - } - } } } } diff --git a/static/less/triggers.less b/static/less/triggers.less new file mode 100644 index 0000000..9fecbe5 --- /dev/null +++ b/static/less/triggers.less @@ -0,0 +1,26 @@ +@import 'theme.less'; + + +#areas { + .area { + .section { + .triggers { + .trigger { + display: grid; + grid-template-columns: min-content 1fr min-content; + grid-gap: 8px; + align-items: center; + margin-top: 8px; + + img { + height: 16px; + } + + .label { + color: inherit; + } + } + } + } + } +} diff --git a/trigger.go b/trigger.go index 6020f12..6525598 100644 --- a/trigger.go +++ b/trigger.go @@ -2,7 +2,7 @@ package main import ( // External - we "git.gibonuddevalla.se/go/wrappederror" + werr "git.gibonuddevalla.se/go/wrappederror" "github.com/expr-lang/expr" "github.com/lib/pq" @@ -67,7 +67,7 @@ func TriggersRetrieve() (areas []Area, err error) { // {{{ var jsonData []byte err = row.Scan(&jsonData) if err != nil { - err = we.Wrap(err) + err = werr.Wrap(err) return } @@ -77,7 +77,7 @@ func TriggersRetrieve() (areas []Area, err error) { // {{{ err = json.Unmarshal(jsonData, &areas) if err != nil { - err = we.Wrap(err) + err = werr.Wrap(err) return } @@ -97,13 +97,13 @@ func TriggersRetrieveByDatapoint(datapointName string) (triggers []Trigger, err var data []byte err = row.Scan(&data) if err != nil { - err = we.Wrap(err).WithData(datapointName) + err = werr.Wrap(err).WithData(datapointName) return } err = json.Unmarshal(data, &triggers) if err != nil { - err = we.Wrap(err).WithData(datapointName) + err = werr.Wrap(err).WithData(datapointName) return } @@ -114,7 +114,7 @@ func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{ var jsonData []byte err = row.Scan(&jsonData) if err != nil { - err = we.Wrap(err) + err = werr.Wrap(err) return } @@ -158,7 +158,7 @@ func (t *Trigger) Update() (err error) { // {{{ ) err = row.Scan(&t.ID) if err != nil { - err = we.Wrap(err).WithData( + err = werr.Wrap(err).WithData( struct { SectionID int Name string @@ -191,7 +191,7 @@ func (t *Trigger) Update() (err error) { // {{{ } if pqErr, ok := err.(*pq.Error); ok { - err = we.Wrap(err).WithData( + err = werr.Wrap(err).WithData( struct { Trigger *Trigger PostgresCode pq.ErrorCode @@ -202,7 +202,14 @@ func (t *Trigger) Update() (err error) { // {{{ pqErr.Code.Name(), }) } else if err != nil { - err = we.Wrap(err).WithData(t) + err = werr.Wrap(err).WithData(t) + } + 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 } // }}} @@ -213,7 +220,7 @@ func (t *Trigger) Run() (output any, err error) { // {{{ var dp Datapoint dp, err = DatapointRetrieve(0, dpname) if err != nil { - err = we.Wrap(err) + err = werr.Wrap(err) return } datapoints[dpname] = dp diff --git a/views/pages/triggers.gotmpl b/views/pages/triggers.gotmpl index 60baf48..a0a5bef 100644 --- a/views/pages/triggers.gotmpl +++ b/views/pages/triggers.gotmpl @@ -2,6 +2,7 @@ {{ block "page_label" . }}{{end}} {{ $version := .VERSION }} +
@@ -42,12 +49,11 @@ {{ if eq .Name "" }} {{ continue }} {{ end }} - -
- -
{{ .Name }}
-
-
+
+ + + +
{{ end }}
From 72f23b9c4d1cac2a713cb0fc8a81b72f5e81c26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Sat, 1 Jun 2024 09:19:10 +0200 Subject: [PATCH 049/152] Bumped to v17 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 90bf8b2..9496441 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( "time" ) -const VERSION = "v16" +const VERSION = "v17" var ( logger *slog.Logger 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 050/152] 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 }}
+ +
{{ 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 051/152] 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 052/152] 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 053/152] #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 }}
From e86c96d78ff8747897a3ca888a8b1d8db6cc8618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Mon, 24 Jun 2024 11:19:16 +0200 Subject: [PATCH 054/152] Bumped to v18 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 569d99f..f31024f 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ import ( "time" ) -const VERSION = "v17" +const VERSION = "v18" var ( logger *slog.Logger From 58e0b2f0818dc5bcfe230d978f7e153c34db7086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Tue, 25 Jun 2024 08:59:07 +0200 Subject: [PATCH 055/152] Theming support --- configuration.go | 40 ++++ main.go | 14 ++ page.go | 3 + sql/00018.sql | 5 + sql/00019.sql | 1 + static/css/configuration.css | 130 ----------- static/css/datapoints.css | 168 -------------- static/css/default_light/configuration.css | 26 +++ static/css/default_light/datapoints.css | 64 ++++++ static/css/default_light/default_light.css | 37 ++++ static/css/default_light/gruvbox.css | 0 static/css/default_light/index.css | 4 + static/css/default_light/main.css | 205 ++++++++++++++++++ static/css/default_light/problems.css | 51 +++++ .../css/default_light/theme-default_light.css | 0 static/css/default_light/theme-gruvbox.css | 0 static/css/default_light/trigger_edit.css | 44 ++++ static/css/default_light/triggers.css | 13 ++ static/css/gruvbox/configuration.css | 26 +++ static/css/gruvbox/datapoints.css | 64 ++++++ static/css/gruvbox/default_light.css | 37 ++++ static/css/gruvbox/gruvbox.css | 0 static/css/gruvbox/index.css | 4 + static/css/{ => gruvbox}/main.css | 182 ++++++++-------- static/css/gruvbox/problems.css | 51 +++++ static/css/gruvbox/theme-default_light.css | 0 static/css/gruvbox/theme-gruvbox.css | 0 static/css/{ => gruvbox}/theme.css | 0 static/css/gruvbox/trigger_edit.css | 44 ++++ static/css/gruvbox/triggers.css | 13 ++ static/css/index.css | 108 --------- static/css/problems.css | 155 ------------- static/css/trigger_edit.css | 148 ------------- static/css/triggers.css | 117 ---------- .../default_light/acknowledge-filled.svg | 1 + .../default_light/acknowledge-outline.svg | 1 + static/images/default_light/acknowledge.svg | 1 + static/images/default_light/configuration.svg | 69 ++++++ .../default_light/configuration_selected.svg | 67 ++++++ static/images/default_light/datapoints.svg | 76 +++++++ .../default_light/datapoints_selected.svg | 80 +++++++ static/images/default_light/delete.svg | 1 + static/images/default_light/delete_white.svg | 1 + static/images/default_light/graph.drawio | 1 + static/images/default_light/graph.svg | 1 + static/images/default_light/info-filled.svg | 1 + static/images/default_light/info-outline.svg | 1 + static/images/default_light/logo.svg | 73 +++++++ static/images/default_light/logo_selected.svg | 71 ++++++ static/images/default_light/problems.svg | 69 ++++++ .../default_light/problems_selected.svg | 67 ++++++ static/images/default_light/triggers.svg | 71 ++++++ .../default_light/triggers_selected.svg | 69 ++++++ static/images/default_light/values.svg | 1 + static/images/gruvbox/acknowledge-filled.svg | 1 + static/images/gruvbox/acknowledge-outline.svg | 1 + static/images/gruvbox/acknowledge.svg | 1 + static/images/gruvbox/configuration.svg | 1 + .../images/gruvbox/configuration_selected.svg | 1 + static/images/gruvbox/datapoints.svg | 1 + static/images/gruvbox/datapoints_selected.svg | 1 + static/images/gruvbox/delete.svg | 1 + static/images/gruvbox/delete_white.svg | 1 + static/images/gruvbox/graph.drawio | 1 + static/images/gruvbox/graph.svg | 1 + static/images/gruvbox/info-filled.svg | 1 + static/images/gruvbox/info-outline.svg | 1 + static/images/gruvbox/logo.svg | 1 + static/images/gruvbox/logo_selected.svg | 1 + static/images/gruvbox/problems.svg | 1 + static/images/gruvbox/problems_selected.svg | 1 + static/images/gruvbox/triggers.svg | 1 + static/images/gruvbox/triggers_selected.svg | 1 + static/images/gruvbox/values.svg | 1 + static/less/Makefile | 6 +- static/less/configuration.less | 2 +- static/less/datapoints.less | 2 +- static/less/default_light.less | 53 +++++ static/less/gruvbox.less | 1 + static/less/index.less | 2 +- static/less/loop_make.sh | 17 +- static/less/main.less | 131 ++++++++++- static/less/problems.less | 2 +- static/less/theme-default_light.less | 20 ++ static/less/theme-gruvbox.less | 20 ++ static/less/theme.less | 143 ------------ static/less/trigger_edit.less | 2 +- static/less/triggers.less | 2 +- views/components/menu.gotmpl | 10 +- views/components/page_label.gotmpl | 2 +- views/layouts/main.gotmpl | 3 +- views/pages/configuration.gotmpl | 2 +- views/pages/datapoint_edit.gotmpl | 2 +- views/pages/datapoint_values.gotmpl | 2 +- views/pages/datapoints.gotmpl | 2 +- views/pages/index.gotmpl | 2 +- views/pages/problems.gotmpl | 2 +- views/pages/trigger_edit.gotmpl | 2 +- views/pages/triggers.gotmpl | 5 +- 99 files changed, 1839 insertions(+), 1094 deletions(-) create mode 100644 configuration.go create mode 100644 sql/00018.sql create mode 100644 sql/00019.sql delete mode 100644 static/css/configuration.css delete mode 100644 static/css/datapoints.css create mode 100644 static/css/default_light/configuration.css create mode 100644 static/css/default_light/datapoints.css create mode 100644 static/css/default_light/default_light.css create mode 100644 static/css/default_light/gruvbox.css create mode 100644 static/css/default_light/index.css create mode 100644 static/css/default_light/main.css create mode 100644 static/css/default_light/problems.css create mode 100644 static/css/default_light/theme-default_light.css create mode 100644 static/css/default_light/theme-gruvbox.css create mode 100644 static/css/default_light/trigger_edit.css create mode 100644 static/css/default_light/triggers.css create mode 100644 static/css/gruvbox/configuration.css create mode 100644 static/css/gruvbox/datapoints.css create mode 100644 static/css/gruvbox/default_light.css create mode 100644 static/css/gruvbox/gruvbox.css create mode 100644 static/css/gruvbox/index.css rename static/css/{ => gruvbox}/main.css (96%) create mode 100644 static/css/gruvbox/problems.css create mode 100644 static/css/gruvbox/theme-default_light.css create mode 100644 static/css/gruvbox/theme-gruvbox.css rename static/css/{ => gruvbox}/theme.css (100%) create mode 100644 static/css/gruvbox/trigger_edit.css create mode 100644 static/css/gruvbox/triggers.css delete mode 100644 static/css/index.css delete mode 100644 static/css/problems.css delete mode 100644 static/css/trigger_edit.css delete mode 100644 static/css/triggers.css create mode 120000 static/images/default_light/acknowledge-filled.svg create mode 120000 static/images/default_light/acknowledge-outline.svg create mode 120000 static/images/default_light/acknowledge.svg create mode 100644 static/images/default_light/configuration.svg create mode 100644 static/images/default_light/configuration_selected.svg create mode 100644 static/images/default_light/datapoints.svg create mode 100644 static/images/default_light/datapoints_selected.svg create mode 120000 static/images/default_light/delete.svg create mode 120000 static/images/default_light/delete_white.svg create mode 120000 static/images/default_light/graph.drawio create mode 120000 static/images/default_light/graph.svg create mode 120000 static/images/default_light/info-filled.svg create mode 120000 static/images/default_light/info-outline.svg create mode 100644 static/images/default_light/logo.svg create mode 100644 static/images/default_light/logo_selected.svg create mode 100644 static/images/default_light/problems.svg create mode 100644 static/images/default_light/problems_selected.svg create mode 100644 static/images/default_light/triggers.svg create mode 100644 static/images/default_light/triggers_selected.svg create mode 120000 static/images/default_light/values.svg create mode 120000 static/images/gruvbox/acknowledge-filled.svg create mode 120000 static/images/gruvbox/acknowledge-outline.svg create mode 120000 static/images/gruvbox/acknowledge.svg create mode 120000 static/images/gruvbox/configuration.svg create mode 120000 static/images/gruvbox/configuration_selected.svg create mode 120000 static/images/gruvbox/datapoints.svg create mode 120000 static/images/gruvbox/datapoints_selected.svg create mode 120000 static/images/gruvbox/delete.svg create mode 120000 static/images/gruvbox/delete_white.svg create mode 120000 static/images/gruvbox/graph.drawio create mode 120000 static/images/gruvbox/graph.svg create mode 120000 static/images/gruvbox/info-filled.svg create mode 120000 static/images/gruvbox/info-outline.svg create mode 120000 static/images/gruvbox/logo.svg create mode 120000 static/images/gruvbox/logo_selected.svg create mode 120000 static/images/gruvbox/problems.svg create mode 120000 static/images/gruvbox/problems_selected.svg create mode 120000 static/images/gruvbox/triggers.svg create mode 120000 static/images/gruvbox/triggers_selected.svg create mode 120000 static/images/gruvbox/values.svg create mode 100644 static/less/default_light.less create mode 100644 static/less/gruvbox.less create mode 100644 static/less/theme-default_light.less create mode 100644 static/less/theme-gruvbox.less delete mode 100644 static/less/theme.less diff --git a/configuration.go b/configuration.go new file mode 100644 index 0000000..889b2fe --- /dev/null +++ b/configuration.go @@ -0,0 +1,40 @@ +package main + +import ( + // External + werr "git.gibonuddevalla.se/go/wrappederror" + + // Standard + "database/sql" +) + +type Configuration struct { + Settings map[string]string +} + +var smonConfig Configuration + +func SmonConfigInit() (err error) { + smonConfig.Settings = make(map[string]string, 8) + + var rows *sql.Rows + rows, err = service.Db.Conn.Query(`SELECT * FROM public.configuration`) + if err != nil { + err = werr.Wrap(err) + return + } + defer rows.Close() + + for rows.Next() { + var setting, value string + err = rows.Scan(&setting, &value) + if err != nil { + err = werr.Wrap(err) + return + } + + smonConfig.Settings[setting] = value + } + + return +} diff --git a/main.go b/main.go index f31024f..21dcedf 100644 --- a/main.go +++ b/main.go @@ -152,6 +152,12 @@ func main() { // {{{ go nodataLoop() + err = SmonConfigInit() + if err != nil { + logger.Error("configuration", "error", werr.Wrap(err)) + os.Exit(1) + } + err = service.Start() if err != nil { logger.Error("webserver", "error", werr.Wrap(err)) @@ -366,6 +372,7 @@ func pageIndex(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page := Page{ LAYOUT: "main", PAGE: "index", + CONFIG: smonConfig.Settings, } page.Render(w) } // }}} @@ -481,6 +488,7 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page := Page{ LAYOUT: "main", PAGE: "problems", + CONFIG: smonConfig.Settings, } problems, err := ProblemsRetrieve() @@ -555,6 +563,7 @@ func pageDatapoints(w http.ResponseWriter, r *http.Request, _ *session.T) { // { page := Page{ LAYOUT: "main", PAGE: "datapoints", + CONFIG: smonConfig.Settings, } datapoints, err := DatapointsRetrieve() @@ -599,6 +608,7 @@ func pageDatapointEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { / page := Page{ LAYOUT: "main", PAGE: "datapoint_edit", + CONFIG: smonConfig.Settings, MENU: "datapoints", Icon: "datapoints", Label: "Datapoint", @@ -679,6 +689,7 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) { page := Page{ LAYOUT: "main", PAGE: "datapoint_values", + CONFIG: smonConfig.Settings, MENU: "datapoints", Icon: "datapoints", Label: "Values for " + datapoint.Name, @@ -706,6 +717,7 @@ func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page := Page{ LAYOUT: "main", PAGE: "triggers", + CONFIG: smonConfig.Settings, Data: map[string]any{ "Areas": areas, }, @@ -790,6 +802,7 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // LAYOUT: "main", PAGE: "trigger_edit", MENU: "triggers", + CONFIG: smonConfig.Settings, Label: "Trigger", Icon: "triggers", Data: map[string]any{ @@ -934,6 +947,7 @@ func pageConfiguration(w http.ResponseWriter, _ *http.Request, _ *session.T) { / page := Page{ LAYOUT: "main", PAGE: "configuration", + CONFIG: smonConfig.Settings, Data: map[string]any{ "Areas": areas, }, diff --git a/page.go b/page.go index 2465645..18e151b 100644 --- a/page.go +++ b/page.go @@ -15,6 +15,7 @@ type Page struct { LAYOUT string PAGE string MENU string + CONFIG map[string]string Label string Icon string @@ -47,6 +48,8 @@ func (p *Page) Render(w http.ResponseWriter) { "LAYOUT": p.LAYOUT, "PAGE": p.PAGE, "MENU": p.MENU, + "CONFIG": smonConfig.Settings, + "Label": p.Label, "Icon": p.Icon, "Data": p.Data, diff --git a/sql/00018.sql b/sql/00018.sql new file mode 100644 index 0000000..c9f5a0c --- /dev/null +++ b/sql/00018.sql @@ -0,0 +1,5 @@ +CREATE TABLE public."configuration" ( + setting varchar NOT NULL, + value varchar DEFAULT '' NOT NULL, + CONSTRAINT configuration_pk PRIMARY KEY (setting) +); diff --git a/sql/00019.sql b/sql/00019.sql new file mode 100644 index 0000000..e564737 --- /dev/null +++ b/sql/00019.sql @@ -0,0 +1 @@ +INSERT INTO public.configuration(setting, value) VALUES('THEME', 'gruvbox'); diff --git a/static/css/configuration.css b/static/css/configuration.css deleted file mode 100644 index e013c47..0000000 --- a/static/css/configuration.css +++ /dev/null @@ -1,130 +0,0 @@ -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/datapoints.css b/static/css/datapoints.css deleted file mode 100644 index 3baa3b0..0000000 --- a/static/css/datapoints.css +++ /dev/null @@ -1,168 +0,0 @@ -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; -} -#datapoints { - display: grid; - grid-template-columns: repeat(6, min-content); - grid-gap: 8px 16px; - margin-top: 16px; -} -#datapoints .group { - font-size: 1.1em; - font-weight: bold; - color: #b8bb26; - margin-top: 1.5em; - padding-bottom: 4px; -} -#datapoints h2 { - border-bottom: unset; -} -#datapoints .header { - font-weight: 800; - font-size: 0.85em; - color: #d5c4a1; -} -#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); - gap: 16px; - white-space: nowrap; -} -.widgets { - display: grid; - grid-template-columns: min-content 1fr; - gap: 8px 16px; -} -.widgets .label { - margin-top: 4px; - white-space: nowrap; -} -.widgets input[type="text"], -.widgets textarea { - width: 100%; -} -.widgets .datapoints { - display: grid; - grid-template-columns: min-content 1fr; - gap: 6px 8px; - font-family: "Roboto Mono", monospace; - margin-bottom: 8px; -} -.widgets .action { - display: grid; - grid-template-columns: min-content min-content; - grid-gap: 8px; -} diff --git a/static/css/default_light/configuration.css b/static/css/default_light/configuration.css new file mode 100644 index 0000000..83a9658 --- /dev/null +++ b/static/css/default_light/configuration.css @@ -0,0 +1,26 @@ +#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/default_light/datapoints.css b/static/css/default_light/datapoints.css new file mode 100644 index 0000000..5ca8d48 --- /dev/null +++ b/static/css/default_light/datapoints.css @@ -0,0 +1,64 @@ +#datapoints { + display: grid; + grid-template-columns: repeat(6, min-content); + grid-gap: 8px 16px; + margin-top: 16px; +} +#datapoints .group { + font-size: 1.1em; + font-weight: bold; + color: #2c6e97; + margin-top: 1.5em; + padding-bottom: 4px; +} +#datapoints h2 { + border-bottom: unset; +} +#datapoints .header { + font-weight: 800; + font-size: 0.85em; + color: #333; +} +#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); + gap: 16px; + white-space: nowrap; +} +.widgets { + display: grid; + grid-template-columns: min-content 1fr; + gap: 8px 16px; +} +.widgets .label { + margin-top: 4px; + white-space: nowrap; +} +.widgets input[type="text"], +.widgets textarea { + width: 100%; +} +.widgets .datapoints { + display: grid; + grid-template-columns: min-content 1fr; + gap: 6px 8px; + font-family: "Roboto Mono", monospace; + margin-bottom: 8px; +} +.widgets .action { + display: grid; + grid-template-columns: min-content min-content; + grid-gap: 8px; +} diff --git a/static/css/default_light/default_light.css b/static/css/default_light/default_light.css new file mode 100644 index 0000000..f1ece36 --- /dev/null +++ b/static/css/default_light/default_light.css @@ -0,0 +1,37 @@ +.widgets .action #run-result { + background-color: #fff !important; + border: 1px solid #ccc; +} +#menu .entry .label { + color: #7bb8eb !important; +} +#menu .entry.selected .label { + color: #fff !important; +} +input[type="text"], +textarea, +select { + border: 1px solid #ccc; +} +.description { + border: 1px solid #ccc; +} +button { + background: #2979b8; + color: #fff; + border: 1px solid #2e84cb; +} +button:focus { + background: #2979b8; +} +#areas .area { + background: #fff !important; + border: 1px solid #2979b8; +} +#areas .area .name { + border-top-left-radius: unset; + border-top-right-radius: unset; +} +#areas .area .section .name { + font-weight: normal; +} diff --git a/static/css/default_light/gruvbox.css b/static/css/default_light/gruvbox.css new file mode 100644 index 0000000..e69de29 diff --git a/static/css/default_light/index.css b/static/css/default_light/index.css new file mode 100644 index 0000000..32a3a60 --- /dev/null +++ b/static/css/default_light/index.css @@ -0,0 +1,4 @@ +.graph { + margin-top: 192px; + border-radius: 16px; +} diff --git a/static/css/default_light/main.css b/static/css/default_light/main.css new file mode 100644 index 0000000..d672af0 --- /dev/null +++ b/static/css/default_light/main.css @@ -0,0 +1,205 @@ +html { + box-sizing: border-box; +} +*, +*:before, +*:after { + box-sizing: inherit; +} +*:focus { + outline: none; +} +[onClick] { + cursor: pointer; +} +#layout { + display: grid; + grid-template-areas: "menu content"; + grid-template-columns: 104px 1fr; + height: 100vh; +} +#menu { + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(32, min-content); + align-items: start; + grid-area: menu; + background: #1b4e78; +} +#menu .entry.selected { + background: #2979b8; +} +#menu .entry.selected a { + color: #000 !important; +} +#menu .entry > a { + display: grid; + justify-items: center; + grid-template-rows: 38px + 16px + ; + padding: 16px; + color: #7bb8eb; + text-decoration: none; +} +#menu .entry > a img { + display: block; + width: 32px; +} +#menu .entry > a .label { + font-size: 0.9em; + font-weight: bold; +} +#page { + grid-area: content; + padding: 32px; +} +#page .page-label { + display: grid; + grid-template-columns: min-content 1fr; + grid-gap: 12px; + align-items: center; + margin-bottom: 32px; +} +#page .page-label div { + font-weight: 800; + font-size: 1.5em; + color: #1b4e78; +} +#page .page-label img { + display: block; +} +#areas { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 16px; +} +#areas .area { + background: #2979b8; + border-radius: 4px; +} +#areas .area > .name { + background: #1b4e78; + color: #fff; + font-weight: 800; + padding: 4px 16px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +#areas .area .section { + margin: 8px 16px; + margin-top: 12px; + margin-bottom: 20px; +} +#areas .area .section:last-child { + margin-bottom: 12px; +} +#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: 800; +} +#areas .area .section > .name { + font-weight: 800; +} +dialog { + background: #1b4e78; + border: 1px solid #3f90d4; + color: #333; + box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.25); +} +html, +body { + margin: 0; + padding: 0; +} +body { + background: #fff; + font-family: sans-serif; + font-weight: 300; + color: #333; + font-size: 11pt; +} +h1, +h2 { + margin-bottom: 4px; +} +h1:first-child, +h2:first-child { + margin-top: 0px; +} +h1 { + font-size: 1.5em; + color: #1b4e78; + font-weight: 800; +} +h2 { + font-size: 1.25em; + color: #2c6e97; + font-weight: 800; +} +a { + color: #2c6e97; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +b { + font-weight: 800; +} +input[type="text"], +textarea, +select { + font-family: monospace; + background: #fff; + color: #333; + padding: 4px 8px; + border: 1px solid #484848; + font-size: 1em; + line-height: 1.5em; +} +button { + background: #1b4e78; + color: #333; + padding: 8px 32px; + border: 1px solid #2e84cb; + font-size: 1em; + height: 3em; +} +button:focus { + background: #2979b8; +} +.line { + grid-column: 1 / -1; + border-bottom: 1px solid #d9d9d9; +} +span.date { + color: #333; + font-weight: 800; +} +span.time { + font-size: 0.9em; + color: #333; +} +span.seconds { + display: none; +} +label { + user-select: none; +} +.description { + border: 1px solid #123450; + color: #2c6e97; + background: #fff; + padding: 4px 8px; + margin-top: 8px; + white-space: nowrap; + width: min-content; + border-radius: 8px; +} diff --git a/static/css/default_light/problems.css b/static/css/default_light/problems.css new file mode 100644 index 0000000..ffe8a97 --- /dev/null +++ b/static/css/default_light/problems.css @@ -0,0 +1,51 @@ +#problems-list, +#acknowledged-list { + display: grid; + grid-template-columns: repeat(6, min-content); + grid-gap: 4px 16px; + margin-bottom: 32px; +} +#problems-list div, +#acknowledged-list div { + white-space: nowrap; + line-height: 24px; +} +#problems-list .header, +#acknowledged-list .header { + font-weight: 800; +} +#problems-list .trigger, +#acknowledged-list .trigger { + color: #1b4e78; + font-weight: 800; +} +#problems-list .acknowledge img, +#acknowledged-list .acknowledge img { + height: 16px; +} +#acknowledged-list.hidden { + display: none; +} +#areas { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 16px; +} +#areas .area .section { + display: grid; + grid-template-columns: repeat(2, min-content); + grid-gap: 8px 12px; +} +#areas .area .section .name { + color: #000; + grid-column: 1 / -1; + font-weight: bold !important; + line-height: 24px; +} +#areas .area .section div { + white-space: nowrap; +} +.hidden { + display: none; +} diff --git a/static/css/default_light/theme-default_light.css b/static/css/default_light/theme-default_light.css new file mode 100644 index 0000000..e69de29 diff --git a/static/css/default_light/theme-gruvbox.css b/static/css/default_light/theme-gruvbox.css new file mode 100644 index 0000000..e69de29 diff --git a/static/css/default_light/trigger_edit.css b/static/css/default_light/trigger_edit.css new file mode 100644 index 0000000..0ba9a92 --- /dev/null +++ b/static/css/default_light/trigger_edit.css @@ -0,0 +1,44 @@ +.widgets { + display: grid; + grid-template-columns: min-content 1fr; + gap: 8px 16px; +} +.widgets .label { + margin-top: 4px; +} +.widgets input[type="text"], +.widgets textarea { + width: 100%; +} +.widgets .datapoints { + font: "Roboto Mono", monospace; + display: grid; + grid-template-columns: min-content min-content 1fr; + gap: 6px 8px; + margin-bottom: 8px; + white-space: nowrap; +} +.widgets .datapoints .invalid { + color: #c83737; +} +.widgets .datapoints .delete img { + height: 16px; +} +.widgets .action { + display: grid; + grid-template-columns: min-content min-content 1fr; + grid-gap: 8px; +} +.widgets .action #run-result { + font-family: 'Roboto Mono', monospace; + margin-left: 16px; + padding: 16px; + background: #1b4e78; + min-height: 8em; +} +.widgets .action #run-result.ok { + color: #333; +} +.widgets .action #run-result.error { + color: #fb4934; +} diff --git a/static/css/default_light/triggers.css b/static/css/default_light/triggers.css new file mode 100644 index 0000000..0e0890f --- /dev/null +++ b/static/css/default_light/triggers.css @@ -0,0 +1,13 @@ +#areas .area .section .triggers .trigger { + display: grid; + grid-template-columns: min-content 1fr min-content; + grid-gap: 8px; + align-items: center; + margin-top: 8px; +} +#areas .area .section .triggers .trigger img { + height: 16px; +} +#areas .area .section .triggers .trigger .label { + color: inherit; +} diff --git a/static/css/gruvbox/configuration.css b/static/css/gruvbox/configuration.css new file mode 100644 index 0000000..83a9658 --- /dev/null +++ b/static/css/gruvbox/configuration.css @@ -0,0 +1,26 @@ +#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/gruvbox/datapoints.css b/static/css/gruvbox/datapoints.css new file mode 100644 index 0000000..4b896e4 --- /dev/null +++ b/static/css/gruvbox/datapoints.css @@ -0,0 +1,64 @@ +#datapoints { + display: grid; + grid-template-columns: repeat(6, min-content); + grid-gap: 8px 16px; + margin-top: 16px; +} +#datapoints .group { + font-size: 1.1em; + font-weight: bold; + color: #b8bb26; + margin-top: 1.5em; + padding-bottom: 4px; +} +#datapoints h2 { + border-bottom: unset; +} +#datapoints .header { + font-weight: 800; + font-size: 0.85em; + color: #d5c4a1; +} +#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); + gap: 16px; + white-space: nowrap; +} +.widgets { + display: grid; + grid-template-columns: min-content 1fr; + gap: 8px 16px; +} +.widgets .label { + margin-top: 4px; + white-space: nowrap; +} +.widgets input[type="text"], +.widgets textarea { + width: 100%; +} +.widgets .datapoints { + display: grid; + grid-template-columns: min-content 1fr; + gap: 6px 8px; + font-family: "Roboto Mono", monospace; + margin-bottom: 8px; +} +.widgets .action { + display: grid; + grid-template-columns: min-content min-content; + grid-gap: 8px; +} diff --git a/static/css/gruvbox/default_light.css b/static/css/gruvbox/default_light.css new file mode 100644 index 0000000..d30536b --- /dev/null +++ b/static/css/gruvbox/default_light.css @@ -0,0 +1,37 @@ +.widgets .action #run-result { + background-color: #fff !important; + border: 1px solid #ccc; +} +#menu .entry .label { + color: #777 !important; +} +#menu .entry.selected .label { + color: #fff !important; +} +input[type="text"], +textarea, +select { + border: 1px solid #ccc; +} +.description { + border: 1px solid #ccc; +} +button { + background: #333; + color: #fff; + border: 1px solid #535353; +} +button:focus { + background: #333; +} +#areas .area { + background: #fff !important; + border: 1px solid #333; +} +#areas .area .name { + border-top-left-radius: unset; + border-top-right-radius: unset; +} +#areas .area .section .name { + font-weight: normal; +} diff --git a/static/css/gruvbox/gruvbox.css b/static/css/gruvbox/gruvbox.css new file mode 100644 index 0000000..e69de29 diff --git a/static/css/gruvbox/index.css b/static/css/gruvbox/index.css new file mode 100644 index 0000000..32a3a60 --- /dev/null +++ b/static/css/gruvbox/index.css @@ -0,0 +1,4 @@ +.graph { + margin-top: 192px; + border-radius: 16px; +} diff --git a/static/css/main.css b/static/css/gruvbox/main.css similarity index 96% rename from static/css/main.css rename to static/css/gruvbox/main.css index c5de4d4..8071027 100644 --- a/static/css/main.css +++ b/static/css/gruvbox/main.css @@ -12,100 +12,10 @@ html { [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; -} #layout { display: grid; grid-template-areas: "menu content"; - grid-template-columns: 96px 1fr; + grid-template-columns: 104px 1fr; height: 100vh; } #menu { @@ -203,3 +113,93 @@ dialog { color: #d5c4a1; box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.25); } +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: #282828; + color: #d5c4a1; + padding: 4px 8px; + border: 1px solid #484848; + 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: #282828; + padding: 4px 8px; + margin-top: 8px; + white-space: nowrap; + width: min-content; + border-radius: 8px; +} diff --git a/static/css/gruvbox/problems.css b/static/css/gruvbox/problems.css new file mode 100644 index 0000000..6cd6c90 --- /dev/null +++ b/static/css/gruvbox/problems.css @@ -0,0 +1,51 @@ +#problems-list, +#acknowledged-list { + display: grid; + grid-template-columns: repeat(6, min-content); + grid-gap: 4px 16px; + margin-bottom: 32px; +} +#problems-list div, +#acknowledged-list div { + white-space: nowrap; + line-height: 24px; +} +#problems-list .header, +#acknowledged-list .header { + font-weight: 800; +} +#problems-list .trigger, +#acknowledged-list .trigger { + color: #fb4934; + font-weight: 800; +} +#problems-list .acknowledge img, +#acknowledged-list .acknowledge img { + height: 16px; +} +#acknowledged-list.hidden { + display: none; +} +#areas { + display: flex; + flex-wrap: wrap; + gap: 12px; + margin-top: 16px; +} +#areas .area .section { + display: grid; + grid-template-columns: repeat(2, min-content); + grid-gap: 8px 12px; +} +#areas .area .section .name { + color: #f7edd7; + grid-column: 1 / -1; + font-weight: bold !important; + line-height: 24px; +} +#areas .area .section div { + white-space: nowrap; +} +.hidden { + display: none; +} diff --git a/static/css/gruvbox/theme-default_light.css b/static/css/gruvbox/theme-default_light.css new file mode 100644 index 0000000..e69de29 diff --git a/static/css/gruvbox/theme-gruvbox.css b/static/css/gruvbox/theme-gruvbox.css new file mode 100644 index 0000000..e69de29 diff --git a/static/css/theme.css b/static/css/gruvbox/theme.css similarity index 100% rename from static/css/theme.css rename to static/css/gruvbox/theme.css diff --git a/static/css/gruvbox/trigger_edit.css b/static/css/gruvbox/trigger_edit.css new file mode 100644 index 0000000..711dc17 --- /dev/null +++ b/static/css/gruvbox/trigger_edit.css @@ -0,0 +1,44 @@ +.widgets { + display: grid; + grid-template-columns: min-content 1fr; + gap: 8px 16px; +} +.widgets .label { + margin-top: 4px; +} +.widgets input[type="text"], +.widgets textarea { + width: 100%; +} +.widgets .datapoints { + font: "Roboto Mono", monospace; + display: grid; + grid-template-columns: min-content min-content 1fr; + gap: 6px 8px; + margin-bottom: 8px; + white-space: nowrap; +} +.widgets .datapoints .invalid { + color: #c83737; +} +.widgets .datapoints .delete img { + height: 16px; +} +.widgets .action { + display: grid; + grid-template-columns: min-content min-content 1fr; + grid-gap: 8px; +} +.widgets .action #run-result { + font-family: 'Roboto Mono', monospace; + margin-left: 16px; + padding: 16px; + background: #202020; + min-height: 8em; +} +.widgets .action #run-result.ok { + color: #d5c4a1; +} +.widgets .action #run-result.error { + color: #fb4934; +} diff --git a/static/css/gruvbox/triggers.css b/static/css/gruvbox/triggers.css new file mode 100644 index 0000000..0e0890f --- /dev/null +++ b/static/css/gruvbox/triggers.css @@ -0,0 +1,13 @@ +#areas .area .section .triggers .trigger { + display: grid; + grid-template-columns: min-content 1fr min-content; + grid-gap: 8px; + align-items: center; + margin-top: 8px; +} +#areas .area .section .triggers .trigger img { + height: 16px; +} +#areas .area .section .triggers .trigger .label { + color: inherit; +} diff --git a/static/css/index.css b/static/css/index.css deleted file mode 100644 index 5c0077f..0000000 --- a/static/css/index.css +++ /dev/null @@ -1,108 +0,0 @@ -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; -} -.graph { - margin-top: 192px; - border-radius: 16px; -} diff --git a/static/css/problems.css b/static/css/problems.css deleted file mode 100644 index afbf302..0000000 --- a/static/css/problems.css +++ /dev/null @@ -1,155 +0,0 @@ -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; -} -#problems-list, -#acknowledged-list { - display: grid; - grid-template-columns: repeat(6, min-content); - grid-gap: 4px 16px; - margin-bottom: 32px; -} -#problems-list div, -#acknowledged-list div { - white-space: nowrap; - line-height: 24px; -} -#problems-list .header, -#acknowledged-list .header { - font-weight: 800; -} -#problems-list .trigger, -#acknowledged-list .trigger { - color: #fb4934; - font-weight: 800; -} -#problems-list .acknowledge img, -#acknowledged-list .acknowledge img { - height: 16px; -} -#acknowledged-list.hidden { - display: none; -} -#areas { - display: flex; - flex-wrap: wrap; - gap: 12px; - margin-top: 16px; -} -#areas .area .section { - display: grid; - grid-template-columns: repeat(2, min-content); - grid-gap: 8px 12px; -} -#areas .area .section .name { - color: #f7edd7; - grid-column: 1 / -1; - font-weight: bold !important; - line-height: 24px; -} -#areas .area .section div { - white-space: nowrap; -} -.hidden { - display: none; -} diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css deleted file mode 100644 index 070964d..0000000 --- a/static/css/trigger_edit.css +++ /dev/null @@ -1,148 +0,0 @@ -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; -} -.widgets { - display: grid; - grid-template-columns: min-content 1fr; - gap: 8px 16px; -} -.widgets .label { - margin-top: 4px; -} -.widgets input[type="text"], -.widgets textarea { - width: 100%; -} -.widgets .datapoints { - font: "Roboto Mono", monospace; - display: grid; - grid-template-columns: min-content min-content 1fr; - gap: 6px 8px; - margin-bottom: 8px; - white-space: nowrap; -} -.widgets .datapoints .invalid { - color: #c83737; -} -.widgets .datapoints .delete img { - height: 16px; -} -.widgets .action { - display: grid; - grid-template-columns: min-content min-content 1fr; - grid-gap: 8px; -} -.widgets .action #run-result { - font-family: 'Roboto Mono', monospace; - margin-left: 16px; - padding: 16px; - background: #202020; - min-height: 8em; -} -.widgets .action #run-result.ok { - color: #d5c4a1; -} -.widgets .action #run-result.error { - color: #fb4934; -} diff --git a/static/css/triggers.css b/static/css/triggers.css deleted file mode 100644 index 352428d..0000000 --- a/static/css/triggers.css +++ /dev/null @@ -1,117 +0,0 @@ -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 .section .triggers .trigger { - display: grid; - grid-template-columns: min-content 1fr min-content; - grid-gap: 8px; - align-items: center; - margin-top: 8px; -} -#areas .area .section .triggers .trigger img { - height: 16px; -} -#areas .area .section .triggers .trigger .label { - color: inherit; -} diff --git a/static/images/default_light/acknowledge-filled.svg b/static/images/default_light/acknowledge-filled.svg new file mode 120000 index 0000000..a0ab2cc --- /dev/null +++ b/static/images/default_light/acknowledge-filled.svg @@ -0,0 +1 @@ +../acknowledge-filled.svg \ No newline at end of file diff --git a/static/images/default_light/acknowledge-outline.svg b/static/images/default_light/acknowledge-outline.svg new file mode 120000 index 0000000..8c2311e --- /dev/null +++ b/static/images/default_light/acknowledge-outline.svg @@ -0,0 +1 @@ +../acknowledge-outline.svg \ No newline at end of file diff --git a/static/images/default_light/acknowledge.svg b/static/images/default_light/acknowledge.svg new file mode 120000 index 0000000..b168727 --- /dev/null +++ b/static/images/default_light/acknowledge.svg @@ -0,0 +1 @@ +../acknowledge.svg \ No newline at end of file diff --git a/static/images/default_light/configuration.svg b/static/images/default_light/configuration.svg new file mode 100644 index 0000000..b7430d2 --- /dev/null +++ b/static/images/default_light/configuration.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + cog + cog-outline + + + diff --git a/static/images/default_light/configuration_selected.svg b/static/images/default_light/configuration_selected.svg new file mode 100644 index 0000000..d76911c --- /dev/null +++ b/static/images/default_light/configuration_selected.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + cog + + + diff --git a/static/images/default_light/datapoints.svg b/static/images/default_light/datapoints.svg new file mode 100644 index 0000000..ec437db --- /dev/null +++ b/static/images/default_light/datapoints.svg @@ -0,0 +1,76 @@ + + + + + + + + file-chart + chart-timeline-variant + + + + + + + diff --git a/static/images/default_light/datapoints_selected.svg b/static/images/default_light/datapoints_selected.svg new file mode 100644 index 0000000..fdb858d --- /dev/null +++ b/static/images/default_light/datapoints_selected.svg @@ -0,0 +1,80 @@ + + + + + + + + file-chart + chart-timeline-variant + + + + + + + + diff --git a/static/images/default_light/delete.svg b/static/images/default_light/delete.svg new file mode 120000 index 0000000..e08e9df --- /dev/null +++ b/static/images/default_light/delete.svg @@ -0,0 +1 @@ +../delete.svg \ No newline at end of file diff --git a/static/images/default_light/delete_white.svg b/static/images/default_light/delete_white.svg new file mode 120000 index 0000000..4a1a536 --- /dev/null +++ b/static/images/default_light/delete_white.svg @@ -0,0 +1 @@ +../delete_white.svg \ No newline at end of file diff --git a/static/images/default_light/graph.drawio b/static/images/default_light/graph.drawio new file mode 120000 index 0000000..2d35e0f --- /dev/null +++ b/static/images/default_light/graph.drawio @@ -0,0 +1 @@ +../graph.drawio \ No newline at end of file diff --git a/static/images/default_light/graph.svg b/static/images/default_light/graph.svg new file mode 120000 index 0000000..4590ffb --- /dev/null +++ b/static/images/default_light/graph.svg @@ -0,0 +1 @@ +../graph.svg \ No newline at end of file diff --git a/static/images/default_light/info-filled.svg b/static/images/default_light/info-filled.svg new file mode 120000 index 0000000..dbbeefa --- /dev/null +++ b/static/images/default_light/info-filled.svg @@ -0,0 +1 @@ +../info-filled.svg \ No newline at end of file diff --git a/static/images/default_light/info-outline.svg b/static/images/default_light/info-outline.svg new file mode 120000 index 0000000..bdbc69f --- /dev/null +++ b/static/images/default_light/info-outline.svg @@ -0,0 +1 @@ +../info-outline.svg \ No newline at end of file diff --git a/static/images/default_light/logo.svg b/static/images/default_light/logo.svg new file mode 100644 index 0000000..a15846a --- /dev/null +++ b/static/images/default_light/logo.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + alert-octagram + alert-octagram-outline + database-alert + database-alert-outline + + + diff --git a/static/images/default_light/logo_selected.svg b/static/images/default_light/logo_selected.svg new file mode 100644 index 0000000..b60f69c --- /dev/null +++ b/static/images/default_light/logo_selected.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + alert-octagram + alert-octagram-outline + database-alert + + + diff --git a/static/images/default_light/problems.svg b/static/images/default_light/problems.svg new file mode 100644 index 0000000..04f5282 --- /dev/null +++ b/static/images/default_light/problems.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + alert-octagram + alert-octagram-outline + + + diff --git a/static/images/default_light/problems_selected.svg b/static/images/default_light/problems_selected.svg new file mode 100644 index 0000000..32283c5 --- /dev/null +++ b/static/images/default_light/problems_selected.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + alert-octagram + + + diff --git a/static/images/default_light/triggers.svg b/static/images/default_light/triggers.svg new file mode 100644 index 0000000..76542fe --- /dev/null +++ b/static/images/default_light/triggers.svg @@ -0,0 +1,71 @@ + + + + + + + + + + image/svg+xml + + + + + + script-text + script-text-outline + calculator-variant-outline + + + diff --git a/static/images/default_light/triggers_selected.svg b/static/images/default_light/triggers_selected.svg new file mode 100644 index 0000000..59d9a72 --- /dev/null +++ b/static/images/default_light/triggers_selected.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + script-text + calculator-variant + + + diff --git a/static/images/default_light/values.svg b/static/images/default_light/values.svg new file mode 120000 index 0000000..6b56d01 --- /dev/null +++ b/static/images/default_light/values.svg @@ -0,0 +1 @@ +../values.svg \ No newline at end of file diff --git a/static/images/gruvbox/acknowledge-filled.svg b/static/images/gruvbox/acknowledge-filled.svg new file mode 120000 index 0000000..a0ab2cc --- /dev/null +++ b/static/images/gruvbox/acknowledge-filled.svg @@ -0,0 +1 @@ +../acknowledge-filled.svg \ No newline at end of file diff --git a/static/images/gruvbox/acknowledge-outline.svg b/static/images/gruvbox/acknowledge-outline.svg new file mode 120000 index 0000000..8c2311e --- /dev/null +++ b/static/images/gruvbox/acknowledge-outline.svg @@ -0,0 +1 @@ +../acknowledge-outline.svg \ No newline at end of file diff --git a/static/images/gruvbox/acknowledge.svg b/static/images/gruvbox/acknowledge.svg new file mode 120000 index 0000000..b168727 --- /dev/null +++ b/static/images/gruvbox/acknowledge.svg @@ -0,0 +1 @@ +../acknowledge.svg \ No newline at end of file diff --git a/static/images/gruvbox/configuration.svg b/static/images/gruvbox/configuration.svg new file mode 120000 index 0000000..4e1aa6a --- /dev/null +++ b/static/images/gruvbox/configuration.svg @@ -0,0 +1 @@ +../configuration.svg \ No newline at end of file diff --git a/static/images/gruvbox/configuration_selected.svg b/static/images/gruvbox/configuration_selected.svg new file mode 120000 index 0000000..027e0f1 --- /dev/null +++ b/static/images/gruvbox/configuration_selected.svg @@ -0,0 +1 @@ +../configuration_selected.svg \ No newline at end of file diff --git a/static/images/gruvbox/datapoints.svg b/static/images/gruvbox/datapoints.svg new file mode 120000 index 0000000..e8426db --- /dev/null +++ b/static/images/gruvbox/datapoints.svg @@ -0,0 +1 @@ +../datapoints.svg \ No newline at end of file diff --git a/static/images/gruvbox/datapoints_selected.svg b/static/images/gruvbox/datapoints_selected.svg new file mode 120000 index 0000000..1fcbab5 --- /dev/null +++ b/static/images/gruvbox/datapoints_selected.svg @@ -0,0 +1 @@ +../datapoints_selected.svg \ No newline at end of file diff --git a/static/images/gruvbox/delete.svg b/static/images/gruvbox/delete.svg new file mode 120000 index 0000000..e08e9df --- /dev/null +++ b/static/images/gruvbox/delete.svg @@ -0,0 +1 @@ +../delete.svg \ No newline at end of file diff --git a/static/images/gruvbox/delete_white.svg b/static/images/gruvbox/delete_white.svg new file mode 120000 index 0000000..4a1a536 --- /dev/null +++ b/static/images/gruvbox/delete_white.svg @@ -0,0 +1 @@ +../delete_white.svg \ No newline at end of file diff --git a/static/images/gruvbox/graph.drawio b/static/images/gruvbox/graph.drawio new file mode 120000 index 0000000..2d35e0f --- /dev/null +++ b/static/images/gruvbox/graph.drawio @@ -0,0 +1 @@ +../graph.drawio \ No newline at end of file diff --git a/static/images/gruvbox/graph.svg b/static/images/gruvbox/graph.svg new file mode 120000 index 0000000..4590ffb --- /dev/null +++ b/static/images/gruvbox/graph.svg @@ -0,0 +1 @@ +../graph.svg \ No newline at end of file diff --git a/static/images/gruvbox/info-filled.svg b/static/images/gruvbox/info-filled.svg new file mode 120000 index 0000000..dbbeefa --- /dev/null +++ b/static/images/gruvbox/info-filled.svg @@ -0,0 +1 @@ +../info-filled.svg \ No newline at end of file diff --git a/static/images/gruvbox/info-outline.svg b/static/images/gruvbox/info-outline.svg new file mode 120000 index 0000000..bdbc69f --- /dev/null +++ b/static/images/gruvbox/info-outline.svg @@ -0,0 +1 @@ +../info-outline.svg \ No newline at end of file diff --git a/static/images/gruvbox/logo.svg b/static/images/gruvbox/logo.svg new file mode 120000 index 0000000..b70ce76 --- /dev/null +++ b/static/images/gruvbox/logo.svg @@ -0,0 +1 @@ +../logo.svg \ No newline at end of file diff --git a/static/images/gruvbox/logo_selected.svg b/static/images/gruvbox/logo_selected.svg new file mode 120000 index 0000000..91070fc --- /dev/null +++ b/static/images/gruvbox/logo_selected.svg @@ -0,0 +1 @@ +../logo_selected.svg \ No newline at end of file diff --git a/static/images/gruvbox/problems.svg b/static/images/gruvbox/problems.svg new file mode 120000 index 0000000..d7b581e --- /dev/null +++ b/static/images/gruvbox/problems.svg @@ -0,0 +1 @@ +../problems.svg \ No newline at end of file diff --git a/static/images/gruvbox/problems_selected.svg b/static/images/gruvbox/problems_selected.svg new file mode 120000 index 0000000..7bf99c0 --- /dev/null +++ b/static/images/gruvbox/problems_selected.svg @@ -0,0 +1 @@ +../problems_selected.svg \ No newline at end of file diff --git a/static/images/gruvbox/triggers.svg b/static/images/gruvbox/triggers.svg new file mode 120000 index 0000000..a347e36 --- /dev/null +++ b/static/images/gruvbox/triggers.svg @@ -0,0 +1 @@ +../triggers.svg \ No newline at end of file diff --git a/static/images/gruvbox/triggers_selected.svg b/static/images/gruvbox/triggers_selected.svg new file mode 120000 index 0000000..3b99184 --- /dev/null +++ b/static/images/gruvbox/triggers_selected.svg @@ -0,0 +1 @@ +../triggers_selected.svg \ No newline at end of file diff --git a/static/images/gruvbox/values.svg b/static/images/gruvbox/values.svg new file mode 120000 index 0000000..6b56d01 --- /dev/null +++ b/static/images/gruvbox/values.svg @@ -0,0 +1 @@ +../values.svg \ No newline at end of file diff --git a/static/less/Makefile b/static/less/Makefile index 7e91a05..4d60bca 100644 --- a/static/less/Makefile +++ b/static/less/Makefile @@ -1,9 +1,9 @@ less = $(wildcard *.less) _css = $(less:.less=.css) -css = $(addprefix ../css/, $(_css) ) +css = $(addprefix ../css/${THEME}/, $(_css) ) -../css/%.css: %.less theme.less - lessc $< ../css/$@ +../css/${THEME}/%.css: %.less theme-${THEME}.less + lessc --global-var="THEME=${THEME}" $< $@ all: $(css) diff --git a/static/less/configuration.less b/static/less/configuration.less index 8c59755..33a8323 100644 --- a/static/less/configuration.less +++ b/static/less/configuration.less @@ -1,4 +1,4 @@ -@import 'theme.less'; +@import 'theme-@{THEME}.less'; #areas { .area { diff --git a/static/less/datapoints.less b/static/less/datapoints.less index 8ecc09a..878623d 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -1,4 +1,4 @@ -@import "theme.less"; +@import "theme-@{THEME}.less"; #datapoints { display: grid; diff --git a/static/less/default_light.less b/static/less/default_light.less new file mode 100644 index 0000000..8afa131 --- /dev/null +++ b/static/less/default_light.less @@ -0,0 +1,53 @@ +@import "theme-@{THEME}.less"; + +.widgets { + .action { + #run-result { + background-color: #fff !important; + border: 1px solid #ccc; + } + } +} + +#menu .entry .label { + color: @text3 !important; +} + +#menu .entry.selected .label { + color: #fff !important; +} + +input[type="text"], +textarea, +select { + border: 1px solid #ccc; +} + +.description { + border: 1px solid #ccc; +} + +button { + background: @bg3; + color: #fff; + border: 1px solid lighten(@bg2, 20%); + &:focus { + background: @bg3; + } +} + +#areas { + .area { + background: #fff !important; + border: 1px solid @bg3; + + .name { + border-top-left-radius: unset; + border-top-right-radius: unset; + } + + .section .name { + font-weight: normal; + } + } +} diff --git a/static/less/gruvbox.less b/static/less/gruvbox.less new file mode 100644 index 0000000..f369797 --- /dev/null +++ b/static/less/gruvbox.less @@ -0,0 +1 @@ +@import "theme-@{THEME}.less"; diff --git a/static/less/index.less b/static/less/index.less index 97eb0bb..015fe1e 100644 --- a/static/less/index.less +++ b/static/less/index.less @@ -1,4 +1,4 @@ -@import "theme.less"; +@import "theme-@{THEME}.less"; .graph { margin-top: 192px; diff --git a/static/less/loop_make.sh b/static/less/loop_make.sh index 0aceed7..afae519 100755 --- a/static/less/loop_make.sh +++ b/static/less/loop_make.sh @@ -6,10 +6,15 @@ do inotifywait -q -e MODIFY *less #sleep 0.5 clear - if make -j12; then - echo -e "\n\e[32;1mOK!\e[0m" - #curl -s http://notes.lan:1371/_ws/css_update - sleep 1 - clear - fi + + for THEME in $(ls theme-*.less | sed -e 's/^theme-\(.*\)\.less$/\1/'); do + if make -j12; then + : + #curl -s http://notes.lan:1371/_ws/css_update + fi + done + echo -e "\n\e[32;1mOK!\e[0m" + sleep 1 + clear + done diff --git a/static/less/main.less b/static/less/main.less index d04e244..17ad78c 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -1,9 +1,27 @@ -@import "theme.less"; +@import "theme-@{THEME}.less"; + +html { + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; +} + +*:focus { + outline: none; +} + +[onClick] { + cursor: pointer; +} #layout { display: grid; grid-template-areas: "menu content"; - grid-template-columns: 96px 1fr; + grid-template-columns: 104px 1fr; height: 100vh; } @@ -30,7 +48,7 @@ 16px ; padding: 16px; - color: #777; + color: @text3; text-decoration: none; img { @@ -122,3 +140,110 @@ dialog { color: @text1; box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.25); } + +html, +body { + margin: 0; + padding: 0; +} + +body { + background: @bg1; + font-family: sans-serif; + font-weight: 300; + color: @text1; + font-size: 11pt; +} + +h1, +h2 { + margin-bottom: 4px; + &:first-child { + margin-top: 0px; + } +} + +h1 { + font-size: 1.5em; + color: @color1; + font-weight: @bold; +} + +h2 { + font-size: 1.25em; + color: @color3; + font-weight: @bold; +} + +a { + color: @color4; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +b { + font-weight: @bold; +} + +input[type="text"], +textarea, +select { + font-family: monospace; + background: @bg1; + color: @text1; + padding: 4px 8px; + border: 1px solid #484848; + font-size: 1em; + line-height: 1.5em; // fix for chrome hiding underscores +} + +button { + background: @bg2; + color: @text1; + padding: 8px 32px; + border: 1px solid lighten(@bg2, 20%); + font-size: 1em; + height: 3em; + + &:focus { + background: @bg3; + } +} + +.line { + grid-column: 1 / -1; + border-bottom: 1px solid .lighterOrDarker(@bg1, 15%)[@result]; +} + +span.date { + color: @text1; + font-weight: @bold; +} + +span.time { + font-size: 0.9em; + color: @text1; +} + +span.seconds { + display: none; +} + +label { + user-select: none; +} + +.description { + border: 1px solid .lighterOrDarker(@bg3, 25%)[@result]; + color: @color4; + background: @bg1; + padding: 4px 8px; + margin-top: 8px; + white-space: nowrap; + width: min-content; + border-radius: 8px; +} + diff --git a/static/less/problems.less b/static/less/problems.less index a6b6551..29caf1f 100644 --- a/static/less/problems.less +++ b/static/less/problems.less @@ -1,4 +1,4 @@ -@import "theme.less"; +@import "theme-@{THEME}.less"; #problems-list, #acknowledged-list { display: grid; diff --git a/static/less/theme-default_light.less b/static/less/theme-default_light.less new file mode 100644 index 0000000..36dca3d --- /dev/null +++ b/static/less/theme-default_light.less @@ -0,0 +1,20 @@ +@bg1: #fff; +@bg2: #1b4e78; +@bg3: #2979b8; + +@text1: #333; +@text2: #000; +@text3: #7bb8eb; + +@error: #fb4934; +@color1: #1b4e78; +@color2: #fabd2f; +@color3: #2c6e97; +@color4: #2c6e97; +@color5: #fe8019; + +@bold: 800; + +.lighterOrDarker(@color, @amount) { + @result: darken(@color, @amount); +} diff --git a/static/less/theme-gruvbox.less b/static/less/theme-gruvbox.less new file mode 100644 index 0000000..5df450d --- /dev/null +++ b/static/less/theme-gruvbox.less @@ -0,0 +1,20 @@ +@bg1: #282828; +@bg2: #202020; +@bg3: #333; + +@text1: #d5c4a1; +@text2: #f7edd7; +@text3: #777; + +@error: #fb4934; +@color1: #fb4934; +@color2: #fabd2f; +@color3: #b8bb26; +@color4: #3f9da1; +@color5: #fe8019; + +@bold: 800; + +.lighterOrDarker(@color, @amount) { + @result: lighten(@color, @amount); +} diff --git a/static/less/theme.less b/static/less/theme.less deleted file mode 100644 index 9a27b23..0000000 --- a/static/less/theme.less +++ /dev/null @@ -1,143 +0,0 @@ -@bg1: #282828; -@bg2: #202020; -@bg3: #333; - -@text1: #d5c4a1; -@text2: #f7edd7; - -@error: #fb4934; -@color1: #fb4934; -@color2: #fabd2f; -@color3: #b8bb26; -@color4: #3f9da1; -@color5: #fe8019; - -@bold: 800; - -.lighterOrDarker(@color, @amount) { - @result: lighten(@color, @amount); -} - -html { - box-sizing: border-box; -} - -*, -*:before, -*:after { - box-sizing: inherit; -} - -*:focus { - outline: none; -} - -[onClick] { - cursor: pointer; -} - -html, -body { - margin: 0; - padding: 0; -} - -body { - background: @bg1; - font-family: sans-serif; - font-weight: 300; - color: @text1; - font-size: 11pt; -} - -h1, -h2 { - margin-bottom: 4px; - &:first-child { - margin-top: 0px; - } -} - -h1 { - font-size: 1.5em; - color: @color1; - font-weight: @bold; -} - -h2 { - font-size: 1.25em; - color: @color3; - font-weight: @bold; -} - -a { - color: @color4; - text-decoration: none; - - &:hover { - text-decoration: underline; - } -} - -b { - font-weight: @bold; -} - -input[type="text"], -textarea, -select { - font-family: monospace; - background: @bg2; - color: @text1; - padding: 4px 8px; - border: none; - font-size: 1em; - line-height: 1.5em; // fix for chrome hiding underscores -} - -button { - background: @bg2; - color: @text1; - padding: 8px 32px; - border: 1px solid lighten(@bg2, 20%); - font-size: 1em; - height: 3em; - - &:focus { - background: @bg3; - } -} - -.line { - grid-column: 1 / -1; - border-bottom: 1px solid .lighterOrDarker(@bg1, 15%)[@result]; -} - -span.date { - color: @text1; - font-weight: @bold; -} - -span.time { - font-size: 0.9em; - color: @text1; -} - -span.seconds { - display: none; -} - -label { - user-select: none; -} - -.description { - border: 1px solid .lighterOrDarker(@bg3, 25%)[@result]; - color: @color4; - background: @bg2; - padding: 4px 8px; - margin-top: 8px; - white-space: nowrap; - width: min-content; - border-radius: 8px; -} diff --git a/static/less/trigger_edit.less b/static/less/trigger_edit.less index 8e48ed9..4e3f0e2 100644 --- a/static/less/trigger_edit.less +++ b/static/less/trigger_edit.less @@ -1,4 +1,4 @@ -@import "theme.less"; +@import "theme-@{THEME}.less"; #dlg-datapoints { } diff --git a/static/less/triggers.less b/static/less/triggers.less index 9fecbe5..b83dfa2 100644 --- a/static/less/triggers.less +++ b/static/less/triggers.less @@ -1,4 +1,4 @@ -@import 'theme.less'; +@import 'theme-@{THEME}.less'; #areas { diff --git a/views/components/menu.gotmpl b/views/components/menu.gotmpl index f00b4d6..c0c1ddf 100644 --- a/views/components/menu.gotmpl +++ b/views/components/menu.gotmpl @@ -2,35 +2,35 @@