Compare commits

...

4 Commits

Author SHA1 Message Date
Magnus Åhall
0f69874475 Bumped to v28 2024-07-04 09:21:46 +02:00
Magnus Åhall
a985f531ea Better datetime display for problems 2024-07-04 09:21:46 +02:00
Magnus Åhall
df714c750b Info icon for problem values 2024-07-04 09:21:46 +02:00
Magnus Åhall
aa368c0b0d Store datapoint values with the problems 2024-07-04 09:21:46 +02:00
8 changed files with 113 additions and 36 deletions

View File

@ -29,7 +29,7 @@ import (
"time" "time"
) )
const VERSION = "v27" const VERSION = "v28"
var ( var (
logger *slog.Logger logger *slog.Logger

View File

@ -7,21 +7,26 @@ import (
// Standard // Standard
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"fmt"
"sort"
"strings"
"time" "time"
) )
type Problem struct { type Problem struct { // {{{
ID int ID int
Start time.Time Start time.Time
End sql.NullTime End sql.NullTime
Acknowledged bool Acknowledged bool
Datapoints map[string]any
DatapointValues map[string]any `json:"datapoints"`
TriggerID int `json:"trigger_id"` TriggerID int `json:"trigger_id"`
TriggerName string `json:"trigger_name"` TriggerName string `json:"trigger_name"`
AreaName string `json:"area_name"` AreaName string `json:"area_name"`
SectionName string `json:"section_name"` SectionName string `json:"section_name"`
} } // }}}
func ProblemsRetrieve() (problems []Problem, err error) { func ProblemsRetrieve() (problems []Problem, err error) { // {{{
problems = []Problem{} problems = []Problem{}
row := service.Db.Conn.QueryRow(` row := service.Db.Conn.QueryRow(`
SELECT SELECT
@ -32,6 +37,7 @@ func ProblemsRetrieve() (problems []Problem, err error) {
p.start, p.start,
p.end, p.end,
p.acknowledged, p.acknowledged,
p.datapoints,
t.id AS trigger_id, t.id AS trigger_id,
t.name AS trigger_name, t.name AS trigger_name,
a.name AS area_name, a.name AS area_name,
@ -52,6 +58,7 @@ func ProblemsRetrieve() (problems []Problem, err error) {
null, null,
null, null,
false, false,
'{}',
-1 AS trigger_id, -1 AS trigger_id,
CONCAT( CONCAT(
'NODATA: ', 'NODATA: ',
@ -83,9 +90,8 @@ func ProblemsRetrieve() (problems []Problem, err error) {
err = we.Wrap(err) err = we.Wrap(err)
} }
return return
} } // }}}
func ProblemStart(trigger Trigger) (problemID int, err error) { // {{{
func ProblemStart(trigger Trigger) (problemID int, err error) {
row := service.Db.Conn.QueryRow(` row := service.Db.Conn.QueryRow(`
SELECT COUNT(id) SELECT COUNT(id)
FROM problem FROM problem
@ -105,16 +111,16 @@ func ProblemStart(trigger Trigger) (problemID int, err error) {
// Open up a new problem if no open exists. // Open up a new problem if no open exists.
if openProblems == 0 { if openProblems == 0 {
row = service.Db.Conn.QueryRow(`INSERT INTO problem(trigger_id) VALUES($1) RETURNING id`, trigger.ID) datapointValuesJson, _ := json.Marshal(trigger.DatapointValues)
row = service.Db.Conn.QueryRow(`INSERT INTO problem(trigger_id, datapoints) VALUES($1, $2) RETURNING id`, trigger.ID, datapointValuesJson)
err = row.Scan(&problemID) err = row.Scan(&problemID)
if err != nil { if err != nil {
err = we.Wrap(err).WithData(trigger) err = we.Wrap(err).WithData(trigger)
} }
} }
return return
} } // }}}
func ProblemClose(trigger Trigger) (problemID int, err error) { // {{{
func ProblemClose(trigger Trigger) (problemID int, err error) {
row := service.Db.Conn.QueryRow(`UPDATE problem SET "end"=NOW() WHERE trigger_id=$1 AND "end" IS NULL RETURNING id`, trigger.ID) row := service.Db.Conn.QueryRow(`UPDATE problem SET "end"=NOW() WHERE trigger_id=$1 AND "end" IS NULL RETURNING id`, trigger.ID)
err = row.Scan(&problemID) err = row.Scan(&problemID)
@ -128,13 +134,44 @@ func ProblemClose(trigger Trigger) (problemID int, err error) {
return return
} }
return return
} } // }}}
func ProblemAcknowledge(id int, state bool) (err error) { // {{{
func ProblemAcknowledge(id int, state bool) (err error) {
_, err = service.Db.Conn.Exec(`UPDATE problem SET "acknowledged"=$2 WHERE id=$1`, id, state) _, err = service.Db.Conn.Exec(`UPDATE problem SET "acknowledged"=$2 WHERE id=$1`, id, state)
if err != nil { if err != nil {
err = we.Wrap(err).WithData(id) err = we.Wrap(err).WithData(id)
return return
} }
return return
} // }}}
func (p Problem) FormattedValues() string {
out := []string{}
for key, val := range p.DatapointValues {
var keyval string
switch val.(type) {
case int:
keyval = fmt.Sprintf("%s: %d", key, val)
case string:
if str, ok := val.(string); ok {
timeVal, err := time.Parse(time.RFC3339, str)
if err == nil {
formattedTime := timeVal.Format("2006-01-02 15:04:05")
keyval = fmt.Sprintf("%s: %s", key, formattedTime)
} else {
keyval = fmt.Sprintf("%s: %s", key, val)
}
}
default:
keyval = fmt.Sprintf("%s: %v", key, val)
}
out = append(out, keyval)
}
sort.Strings(out)
return strings.Join(out, "\n")
} }

4
sql/00022.sql Normal file
View File

@ -0,0 +1,4 @@
ALTER TABLE public.problem ALTER COLUMN trigger_id DROP NOT NULL;
ALTER TABLE public.problem ADD COLUMN datapoints JSONB NOT NULL DEFAULT '{}';
ALTER TABLE public.problem DROP CONSTRAINT problem_trigger_fk;
ALTER TABLE public.problem ADD CONSTRAINT problem_trigger_fk FOREIGN KEY (trigger_id) REFERENCES public."trigger"(id) ON DELETE SET NULL ON UPDATE CASCADE;

View File

@ -30,6 +30,10 @@
#acknowledged-list .acknowledge img { #acknowledged-list .acknowledge img {
height: 16px; height: 16px;
} }
#problems-list .info,
#acknowledged-list .info {
margin-right: 8px;
}
#acknowledged-list.hidden { #acknowledged-list.hidden {
display: none; display: none;
} }
@ -41,7 +45,7 @@
} }
#areas .area .section { #areas .area .section {
display: grid; display: grid;
grid-template-columns: repeat(2, min-content); grid-template-columns: repeat(3, min-content);
grid-gap: 8px 12px; grid-gap: 8px 12px;
} }
#areas .area .section .name { #areas .area .section .name {

View File

@ -30,6 +30,10 @@
#acknowledged-list .acknowledge img { #acknowledged-list .acknowledge img {
height: 16px; height: 16px;
} }
#problems-list .info,
#acknowledged-list .info {
margin-right: 8px;
}
#acknowledged-list.hidden { #acknowledged-list.hidden {
display: none; display: none;
} }
@ -41,7 +45,7 @@
} }
#areas .area .section { #areas .area .section {
display: grid; display: grid;
grid-template-columns: repeat(2, min-content); grid-template-columns: repeat(3, min-content);
grid-gap: 8px 12px; grid-gap: 8px 12px;
} }
#areas .area .section .name { #areas .area .section .name {

View File

@ -32,6 +32,10 @@
height: 16px; height: 16px;
} }
} }
.info {
margin-right: 8px;
}
} }
#acknowledged-list.hidden{ #acknowledged-list.hidden{
@ -47,7 +51,7 @@
.area { .area {
.section { .section {
display: grid; display: grid;
grid-template-columns: repeat(2, min-content); grid-template-columns: repeat(3, min-content);
grid-gap: 8px 12px; grid-gap: 8px 12px;
.name { .name {

View File

@ -19,6 +19,7 @@ type Trigger struct {
SectionID int `db:"section_id"` SectionID int `db:"section_id"`
Expression string Expression string
Datapoints []string Datapoints []string
DatapointValues map[string]any
} }
func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{ func TriggerCreate(sectionID int, name string) (t Trigger, err error) { // {{{
@ -231,9 +232,9 @@ func (t *Trigger) Run() (output any, err error) { // {{{
datapoints[dpname] = dp datapoints[dpname] = dp
} }
env := make(map[string]any) t.DatapointValues = make(map[string]any)
for dpName, dp := range datapoints { for dpName, dp := range datapoints {
env[dpName] = dp.LastDatapointValue.Value() t.DatapointValues[dpName] = dp.LastDatapointValue.Value()
} }
program, err := expr.Compile(t.Expression) program, err := expr.Compile(t.Expression)
@ -241,7 +242,7 @@ func (t *Trigger) Run() (output any, err error) { // {{{
return return
} }
output, err = expr.Run(program, env) output, err = expr.Run(program, t.DatapointValues)
if err != nil { if err != nil {
return return
} }

View File

@ -34,13 +34,26 @@
<div class="area">{{ .AreaName }}</div> <div class="area">{{ .AreaName }}</div>
<div class="section">{{ .SectionName }}</div> <div class="section">{{ .SectionName }}</div>
<div class="start"></div> <div class="start"></div>
<div class="acknowledge"><img src="/images/{{ $version }}/{{ $theme }}/acknowledge.svg"></div> <div class="acknowledge">
<img class="info" src="/images/{{ $version }}/{{ $theme }}/info-outline.svg">
<img src="/images/{{ $version }}/{{ $theme }}/acknowledge.svg">
</div>
{{ else }} {{ else }}
<div class="trigger"><a href="/trigger/edit/{{ .TriggerID }}">{{ .TriggerName }}</a></div> <div class="trigger"><a href="/trigger/edit/{{ .TriggerID }}">{{ .TriggerName }}</a></div>
<div class="area">{{ .AreaName }}</div> <div class="area">{{ .AreaName }}</div>
<div class="section">{{ .SectionName }}</div> <div class="section">{{ .SectionName }}</div>
<div class="start">{{ format_time .Start }}</div> <div class="start">{{ format_time .Start }}</div>
<div class="acknowledge"><a href="/problem/acknowledge/{{ .ID }}"><img src="/images/{{ $version }}/{{ $theme }}/acknowledge-filled.svg"></a></div> <div class="acknowledge">
{{ if .FormattedValues }}
<img class="info" src="/images/{{ $version }}/{{ $theme }}/info-filled.svg" title="{{ .FormattedValues }}">
{{ else }}
<img class="info" src="/images/{{ $version }}/{{ $theme }}/info-outline.svg">
{{ end }}
<a href="/problem/acknowledge/{{ .ID }}">
<img src="/images/{{ $version }}/{{ $theme }}/acknowledge-filled.svg">
</a>
</div>
{{ end }} {{ end }}
{{ end }} {{ end }}
</div> </div>
@ -63,7 +76,9 @@
<div class="area">{{ .AreaName }}</div> <div class="area">{{ .AreaName }}</div>
<div class="section">{{ .SectionName }}</div> <div class="section">{{ .SectionName }}</div>
<div class="start">{{ format_time .Start }}</div> <div class="start">{{ format_time .Start }}</div>
<div class="acknowledge"><a href="/problem/unacknowledge/{{ .ID }}"><img src="/images/{{ $version }}/{{ $theme }}/acknowledge-outline.svg"></a></div> <div class="acknowledge">
<a href="/problem/unacknowledge/{{ .ID }}"><img src="/images/{{ $version }}/{{ $theme }}/acknowledge-outline.svg"></a>
</div>
{{ end }} {{ end }}
</div> </div>
</div> </div>
@ -79,6 +94,7 @@
<div class="name">{{ $sectionName }}</div> <div class="name">{{ $sectionName }}</div>
{{ range $problems }} {{ range $problems }}
<div class="trigger">{{ .TriggerName }}</div> <div class="trigger">{{ .TriggerName }}</div>
{{ if eq (.Start | html) "0001-01-01 00:00:00 +0000 UTC" }} {{ if eq (.Start | html) "0001-01-01 00:00:00 +0000 UTC" }}
@ -86,6 +102,13 @@
{{ else }} {{ else }}
<div class="since">{{ format_time .Start }}</div> <div class="since">{{ format_time .Start }}</div>
{{ end }} {{ end }}
{{ if .FormattedValues }}
<div><img src="/images/{{ $version }}/{{ $theme }}/info-filled.svg" title="{{ .FormattedValues }}"></div>
{{ else }}
<div><img src="/images/{{ $version }}/{{ $theme }}/info-outline.svg"></div>
{{ end }}
{{ end }} {{ end }}
</div> </div>
{{ end }} {{ end }}