diff --git a/main.go b/main.go index 9e9e62d..60e1317 100644 --- a/main.go +++ b/main.go @@ -95,14 +95,11 @@ func main() { // {{{ logger.Error("application", "error", err) return } - _, err = service.Db.Conn.Exec(`SET TIMEZONE TO 'Europe/Stockholm'`) - if err != nil { - logger.Error("application", "error", err) - return - } service.Register("/", false, false, staticHandler) service.Register("/problems", false, false, pageProblems) + service.Register("/problem/acknowledge/{id}", false, false, pageProblemAcknowledge) + service.Register("/problem/unacknowledge/{id}", false, false, pageProblemUnacknowledge) service.Register("/datapoints", false, false, pageDatapoints) service.Register("/datapoint/edit/{id}", false, false, pageDatapointEdit) @@ -247,8 +244,10 @@ func getPage(layout, page string) (tmpl *template.Template, err error) { // {{{ } funcMap := template.FuncMap{ - "format_time": func(t time.Time) string { - return t.Local().Format("2006-01-02 15:04:05") + "format_time": func(t time.Time) template.HTML { + return template.HTML( + t.Local().Format(`2006-01-02 15:04:05`), + ) }, } @@ -283,25 +282,56 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ PAGE: "problems", } - /* - areas, err := ProblemsRetrieve() - if err != nil { - httpError(w, we.Wrap(err).Log()) - return - } - sort.SliceStable(areas, func(i, j int) bool { - return areas[i].Name < areas[j].Name - }) - - logger.Info("problems", "areas", areas) - */ + problems, err := ProblemsRetrieve() + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } page.Data = map[string]any{ - //"Areas": areas, + "Problems": problems, } page.Render(w) return } // }}} +func pageProblemAcknowledge(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 = ProblemAcknowledge(id, true) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + + w.Header().Add("Location", "/problems") + w.WriteHeader(302) + + return +} // }}} +func pageProblemUnacknowledge(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 = ProblemAcknowledge(id, false) + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + + w.Header().Add("Location", "/problems") + w.WriteHeader(302) + + return +} // }}} func pageDatapoints(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ page := Page{ diff --git a/problem.go b/problem.go index ab3b149..5d6f0b5 100644 --- a/problem.go +++ b/problem.go @@ -6,17 +6,61 @@ import ( // Standard "database/sql" + "encoding/json" + "time" ) -/* type Problem struct { - ID int - Name string - SectionID int - Expression string - DatapointNames []string + ID int + Start time.Time + End sql.NullTime + Acknowledged bool + TriggerID int `json:"trigger_id"` + TriggerName string `json:"trigger_name"` + AreaName string `json:"area_name"` + SectionName string `json:"section_name"` +} + +func ProblemsRetrieve() (problems []Problem, err error) { + problems = []Problem{} + row := service.Db.Conn.QueryRow(` + SELECT + jsonb_agg(p.*) + FROM ( + SELECT + p.id, + p.start, + p.end, + p.acknowledged, + t.id AS trigger_id, + t.name AS trigger_name, + a.name AS area_name, + s.name AS section_name + FROM problem p + 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 + ) p + `) + + var jsonBody []byte + err = row.Scan(&jsonBody) + if err != nil { + err = we.Wrap(err) + return + } + + err = json.Unmarshal(jsonBody, &problems) + if err != nil { + err = we.Wrap(err) + } + return } -*/ func ProblemStart(trigger Trigger) (err error) { row := service.Db.Conn.QueryRow(` @@ -54,3 +98,12 @@ func ProblemClose(trigger Trigger) (err error) { } return } + +func ProblemAcknowledge(id int, state bool) (err error) { + _, err = service.Db.Conn.Exec(`UPDATE problem SET "acknowledged"=$2 WHERE id=$1`, id, state) + if err != nil { + err = we.Wrap(err).WithData(id) + return + } + return +} diff --git a/static/css/datapoints.css b/static/css/datapoints.css index ccc0c2a..8230ed8 100644 --- a/static/css/datapoints.css +++ b/static/css/datapoints.css @@ -77,6 +77,24 @@ button { 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; +} #datapoints { display: grid; grid-template-columns: repeat(4, min-content); @@ -102,10 +120,10 @@ button:focus { width: 100%; } .widgets .datapoints { - font: "Roboto Mono", monospace; display: grid; grid-template-columns: min-content 1fr; gap: 6px 8px; + font-family: "Roboto Mono", monospace; margin-bottom: 8px; } .widgets .action { diff --git a/static/css/main.css b/static/css/main.css index b29b9bb..cf2c7e5 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -77,6 +77,24 @@ button { 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; +} #layout { display: grid; grid-template-areas: "menu content"; diff --git a/static/css/problems.css b/static/css/problems.css new file mode 100644 index 0000000..48d1c30 --- /dev/null +++ b/static/css/problems.css @@ -0,0 +1,124 @@ +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: "Roboto", sans-serif; + font-weight: 300; + color: #d5c4a1; + font-size: 11pt; +} +h1, +h2 { + margin-top: 0px; + margin-bottom: 4px; +} +h1 { + font-size: 1.5em; + color: #fb4934; +} +h2 { + font-size: 1.25em; +} +a { + color: #fabd2f; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +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; + background: #202020; + color: #d5c4a1; + padding: 4px 8px; + border: none; + font-size: 1em; +} +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; +} +#problems-list, +#acknowledged-list { + display: grid; + grid-template-columns: repeat(5, min-content); + grid-gap: 4px 16px; + margin-bottom: 32px; +} +#problems-list div, +#acknowledged-list div { + white-space: nowrap; +} +#problems-list .header, +#acknowledged-list .header { + font-weight: 500; +} +#problems-list .trigger, +#acknowledged-list .trigger { + color: #fb4934; + font-weight: 500; +} +#problems-list .acknowledge img, +#acknowledged-list .acknowledge img { + height: 16px; +} +#acknowledged-list.hidden { + display: none; +} diff --git a/static/css/theme.css b/static/css/theme.css index 1c0dada..bfc6ab6 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -77,3 +77,21 @@ button { 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; +} diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index e5676af..0beff8f 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -77,6 +77,24 @@ button { 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; +} .widgets { display: grid; grid-template-columns: min-content 1fr; diff --git a/static/images/acknowledge-filled.svg b/static/images/acknowledge-filled.svg new file mode 100644 index 0000000..f2d8a57 --- /dev/null +++ b/static/images/acknowledge-filled.svg @@ -0,0 +1,67 @@ + + + + + + + + + + image/svg+xml + + + + + + thumb-up + + + diff --git a/static/images/acknowledge-outline.svg b/static/images/acknowledge-outline.svg new file mode 100644 index 0000000..7e97bfb --- /dev/null +++ b/static/images/acknowledge-outline.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + thumb-up + thumb-up-outline + + + diff --git a/static/js/problems.mjs b/static/js/problems.mjs new file mode 100644 index 0000000..54c4e3c --- /dev/null +++ b/static/js/problems.mjs @@ -0,0 +1,23 @@ +export class UI { + constructor() { + const showAcked = localStorage.getItem('show_acknowledged') + if (showAcked == 'true') { + document.getElementById('show-acked').checked = true + const list = document.getElementById('acknowledged-list') + list.classList.remove('hidden') + } + + } + + toggleAcknowledged(evt) { + const list = document.getElementById('acknowledged-list') + + if (evt.target.checked) { + list.classList.remove('hidden') + localStorage.setItem('show_acknowledged', true) + } else { + list.classList.add('hidden') + localStorage.setItem('show_acknowledged', false) + } + } +} diff --git a/static/less/datapoints.less b/static/less/datapoints.less index afa0b13..2bffef9 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -29,10 +29,10 @@ } .datapoints { - font: "Roboto Mono", monospace; display: grid; grid-template-columns: min-content 1fr; gap: 6px 8px; + font-family: "Roboto Mono", monospace; margin-bottom: 8px; } diff --git a/static/less/problems.less b/static/less/problems.less new file mode 100644 index 0000000..f66f9c9 --- /dev/null +++ b/static/less/problems.less @@ -0,0 +1,31 @@ +@import "theme.less"; + +#problems-list, #acknowledged-list { + display: grid; + grid-template-columns: repeat(5, min-content); + grid-gap: 4px 16px; + margin-bottom: 32px; + + div { + white-space: nowrap; + } + + .header { + font-weight: @bold; + } + + .trigger { + color: @color1; + font-weight: @bold; + } + + .acknowledge { + img { + height: 16px; + } + } +} + +#acknowledged-list.hidden{ + display: none; +} diff --git a/static/less/theme.less b/static/less/theme.less index b9e30a2..dd0c723 100644 --- a/static/less/theme.less +++ b/static/less/theme.less @@ -14,12 +14,18 @@ @bold: 500; -html { - box-sizing: border-box; +.lighterOrDarker(@color, @amount) { + @result: lighten(@color, @amount); } -*, *:before, *:after { - box-sizing: inherit; +html { + box-sizing: border-box; +} + +*, +*:before, +*:after { + box-sizing: inherit; } *:focus { @@ -27,7 +33,7 @@ html { } [onClick] { - cursor: pointer; + cursor: pointer; } html, @@ -44,7 +50,8 @@ body { font-size: 11pt; } -h1, h2 { +h1, +h2 { margin-top: 0px; margin-bottom: 4px; } @@ -83,7 +90,9 @@ b { font-style: normal; } -input[type="text"], textarea, select { +input[type="text"], +textarea, +select { font-family: "Roboto Mono", monospace; background: @bg2; color: @text1; @@ -104,3 +113,26 @@ button { 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; +} diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl index bde7414..a3c073f 100644 --- a/views/pages/datapoints.gotmpl +++ b/views/pages/datapoints.gotmpl @@ -12,6 +12,7 @@
Value
{{ range .Data.Datapoints }} +
{{ .Name }}
{{ .Datatype }}
{{ format_time .LastValue }}
diff --git a/views/pages/problems.gotmpl b/views/pages/problems.gotmpl index 0b8c995..45307fd 100644 --- a/views/pages/problems.gotmpl +++ b/views/pages/problems.gotmpl @@ -1,6 +1,56 @@ {{ define "page" }} + {{ $version := .VERSION }} + + + {{ block "page_label" . }}{{end}} +
+

Current

+ +
Trigger
+
Area
+
Section
+
Since
+ {{ range .Data.Problems }} + {{ if .Acknowledged }} + {{ continue }} + {{ end }} +
+
{{ .TriggerName }}
+
{{ .AreaName }}
+
{{ .SectionName }}
+
{{ format_time .Start }}
+
+ {{ end }} +
+ + + + +
{{ range .Data.Areas }}