Compare commits
No commits in common. "d935eb282bfbd79e0b36d2d9d7c5d03c39a657e0" and "965e2daeb37523cc8b00435ce55550b9d05dc4dc" have entirely different histories.
d935eb282b
...
965e2daeb3
@ -36,7 +36,6 @@ type DatapointValue struct {
|
|||||||
ValueInt sql.NullInt64 `db:"value_int"`
|
ValueInt sql.NullInt64 `db:"value_int"`
|
||||||
ValueString sql.NullString `db:"value_string"`
|
ValueString sql.NullString `db:"value_string"`
|
||||||
ValueDateTime sql.NullTime `db:"value_datetime"`
|
ValueDateTime sql.NullTime `db:"value_datetime"`
|
||||||
TemplateValue any
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp DatapointValue) Value() any { // {{{
|
func (dp DatapointValue) Value() any { // {{{
|
||||||
@ -54,13 +53,13 @@ func (dp DatapointValue) Value() any { // {{{
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
} // }}}
|
} // }}}
|
||||||
func (dp DatapointValue) FormattedTime() string {// {{{
|
func (dp DatapointValue) FormattedTime() string {
|
||||||
if dp.ValueDateTime.Valid {
|
if dp.ValueDateTime.Valid {
|
||||||
return dp.ValueDateTime.Time.Format("2006-01-02 15:04:05")
|
return dp.ValueDateTime.Time.Format("2006-01-02 15:04:05")
|
||||||
}
|
}
|
||||||
return "invalid time"
|
return "invalid time"
|
||||||
}// }}}
|
}
|
||||||
func (dp Datapoint) Update() (err error) {// {{{
|
func (dp Datapoint) Update() (err error) {
|
||||||
name := strings.TrimSpace(dp.Name)
|
name := strings.TrimSpace(dp.Name)
|
||||||
if name == "" {
|
if name == "" {
|
||||||
err = errors.New("Name can't be empty")
|
err = errors.New("Name can't be empty")
|
||||||
@ -83,7 +82,7 @@ func (dp Datapoint) Update() (err error) {// {{{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}// }}}
|
}
|
||||||
|
|
||||||
func DatapointAdd[T any](name string, value T) (err error) { // {{{
|
func DatapointAdd[T any](name string, value T) (err error) { // {{{
|
||||||
row := service.Db.Conn.QueryRow(`SELECT id, datatype FROM datapoint WHERE name=$1`, name)
|
row := service.Db.Conn.QueryRow(`SELECT id, datatype FROM datapoint WHERE name=$1`, name)
|
||||||
|
156
main.go
156
main.go
@ -95,22 +95,21 @@ func main() { // {{{
|
|||||||
logger.Error("application", "error", err)
|
logger.Error("application", "error", err)
|
||||||
return
|
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("/", false, false, staticHandler)
|
||||||
service.Register("/problems", false, false, pageProblems)
|
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("/datapoints", false, false, pageDatapoints)
|
||||||
service.Register("/datapoint/edit/{id}", false, false, pageDatapointEdit)
|
service.Register("/datapoint/edit/{id}", false, false, pageDatapointEdit)
|
||||||
service.Register("/datapoint/update/{id}", false, false, pageDatapointUpdate)
|
service.Register("/datapoint/update/{id}", false, false, pageDatapointUpdate)
|
||||||
|
|
||||||
service.Register("/triggers", false, false, pageTriggers)
|
service.Register("/triggers", false, false, pageTriggers)
|
||||||
service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit)
|
service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit)
|
||||||
service.Register("/trigger/edit/{id}/{sectionID}", false, false, pageTriggerEdit)
|
|
||||||
service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate)
|
service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate)
|
||||||
service.Register("/trigger/run/{id}", false, false, pageTriggerRun)
|
service.Register("/trigger/run/{id}", false, false, pageTriggerRun)
|
||||||
|
|
||||||
service.Register("/configuration", false, false, pageConfiguration)
|
service.Register("/configuration", false, false, pageConfiguration)
|
||||||
service.Register("/entry/{datapoint}", false, false, entryDatapoint)
|
service.Register("/entry/{datapoint}", false, false, entryDatapoint)
|
||||||
|
|
||||||
@ -171,45 +170,6 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { /
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var triggers []Trigger
|
|
||||||
triggers, err = TriggersRetrieveByDatapoint(dpoint)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("entry", "error", err)
|
|
||||||
}
|
|
||||||
for _, trigger := range triggers {
|
|
||||||
var out any
|
|
||||||
out, err = trigger.Run()
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).Log()
|
|
||||||
logger.Error("entry", "error", err)
|
|
||||||
|
|
||||||
}
|
|
||||||
logger.Debug("entry", "datapoint", dpoint, "value", value, "trigger", trigger, "result", out)
|
|
||||||
|
|
||||||
switch v := out.(type) {
|
|
||||||
case bool:
|
|
||||||
// Trigger returning true - a problem occurred
|
|
||||||
if v {
|
|
||||||
err = ProblemStart(trigger)
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).Log()
|
|
||||||
logger.Error("entry", "error", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
err = ProblemClose(trigger)
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).Log()
|
|
||||||
logger.Error("entry", "error", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf(`Expression for trigger %s not returning bool (%T)`, trigger.Name, v)
|
|
||||||
logger.Info("entry", "error", err)
|
|
||||||
we.Wrap(err).WithData(v).Log()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
j, _ := json.Marshal(struct{ OK bool }{true})
|
j, _ := json.Marshal(struct{ OK bool }{true})
|
||||||
w.Write(j)
|
w.Write(j)
|
||||||
} // }}}
|
} // }}}
|
||||||
@ -244,10 +204,8 @@ func getPage(layout, page string) (tmpl *template.Template, err error) { // {{{
|
|||||||
}
|
}
|
||||||
|
|
||||||
funcMap := template.FuncMap{
|
funcMap := template.FuncMap{
|
||||||
"format_time": func(t time.Time) template.HTML {
|
"format_time": func(t time.Time) string {
|
||||||
return template.HTML(
|
return t.Local().Format("2006-01-02 15:04:05")
|
||||||
t.Local().Format(`<span class="date">2006-01-02</span> <span class="time">15:04<span class="seconds">:05</span></span>`),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,65 +233,32 @@ func pageIndex(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
|||||||
}
|
}
|
||||||
page.Render(w)
|
page.Render(w)
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||||
page := Page{
|
page := Page{
|
||||||
LAYOUT: "main",
|
LAYOUT: "main",
|
||||||
PAGE: "problems",
|
PAGE: "problems",
|
||||||
}
|
}
|
||||||
|
|
||||||
problems, err := ProblemsRetrieve()
|
/*
|
||||||
|
areas, err := ProblemsRetrieve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
sort.SliceStable(areas, func(i, j int) bool {
|
||||||
|
return areas[i].Name < areas[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.Info("problems", "areas", areas)
|
||||||
|
*/
|
||||||
|
|
||||||
page.Data = map[string]any{
|
page.Data = map[string]any{
|
||||||
"Problems": problems,
|
//"Areas": areas,
|
||||||
}
|
}
|
||||||
page.Render(w)
|
page.Render(w)
|
||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
func pageProblemAcknowledge(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
func pageDatapoints(w http.ResponseWriter, _ *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{
|
page := Page{
|
||||||
LAYOUT: "main",
|
LAYOUT: "main",
|
||||||
PAGE: "datapoints",
|
PAGE: "datapoints",
|
||||||
@ -344,13 +269,7 @@ func pageDatapoints(w http.ResponseWriter, r *http.Request, _ *session.T) { // {
|
|||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
logger.Info("FOO", "dps", datapoints)
|
||||||
// The datapoint selector in trigger edit wants the raw data in JSON.
|
|
||||||
if r.URL.Query().Get("format") == "json" {
|
|
||||||
j, _ := json.Marshal(datapoints)
|
|
||||||
w.Write(j)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
page.Data = map[string]any{
|
page.Data = map[string]any{
|
||||||
"Datapoints": datapoints,
|
"Datapoints": datapoints,
|
||||||
@ -413,7 +332,6 @@ func pageDatapointUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) {
|
|||||||
w.Header().Add("Location", "/datapoints")
|
w.Header().Add("Location", "/datapoints")
|
||||||
w.WriteHeader(302)
|
w.WriteHeader(302)
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||||
areas, err := TriggersRetrieve()
|
areas, err := TriggersRetrieve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -443,27 +361,12 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { //
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating a new trigger uses the edit function.
|
|
||||||
// ID == 0 - create a new trigger.
|
|
||||||
// ID > 0 - edit existing trigger.
|
|
||||||
var trigger Trigger
|
var trigger Trigger
|
||||||
if id > 0 {
|
|
||||||
trigger, err = TriggerRetrieve(id)
|
trigger, err = TriggerRetrieve(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// A new trigger needs to know which section it belongs to.
|
|
||||||
sectionIDStr := r.PathValue("sectionID")
|
|
||||||
if sectionIDStr != "" {
|
|
||||||
trigger.SectionID, err = strconv.Atoi(sectionIDStr)
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, we.Wrap(err).Log())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
datapoints := make(map[string]Datapoint)
|
datapoints := make(map[string]Datapoint)
|
||||||
for _, dpname := range trigger.Datapoints {
|
for _, dpname := range trigger.Datapoints {
|
||||||
@ -472,7 +375,6 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { //
|
|||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dp.LastDatapointValue.TemplateValue = dp.LastDatapointValue.Value()
|
|
||||||
datapoints[dpname] = dp
|
datapoints[dpname] = dp
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,23 +401,14 @@ func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { /
|
|||||||
}
|
}
|
||||||
|
|
||||||
var trigger Trigger
|
var trigger Trigger
|
||||||
if id > 0 {
|
|
||||||
trigger, err = TriggerRetrieve(id)
|
trigger, err = TriggerRetrieve(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
trigger.SectionID, err = strconv.Atoi(r.FormValue("sectionID"))
|
|
||||||
if err != nil {
|
|
||||||
httpError(w, we.Wrap(err).Log())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trigger.Name = r.FormValue("name")
|
trigger.Name = r.FormValue("name")
|
||||||
trigger.Expression = r.FormValue("expression")
|
trigger.Expression = r.FormValue("expression")
|
||||||
trigger.Datapoints = r.Form["datapoints[]"]
|
|
||||||
err = trigger.Update()
|
err = trigger.Update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
@ -543,13 +436,23 @@ func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {
|
|||||||
expr, _ := io.ReadAll(r.Body)
|
expr, _ := io.ReadAll(r.Body)
|
||||||
trigger.Expression = string(expr)
|
trigger.Expression = string(expr)
|
||||||
|
|
||||||
|
datapoints := make(map[string]Datapoint)
|
||||||
|
for _, dpname := range trigger.Datapoints {
|
||||||
|
dp, err := DatapointRetrieve(0, dpname)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, we.Wrap(err).Log())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
datapoints[dpname] = dp
|
||||||
|
}
|
||||||
|
|
||||||
resp := struct {
|
resp := struct {
|
||||||
OK bool
|
OK bool
|
||||||
Output any
|
Output any
|
||||||
}{
|
}{
|
||||||
OK: true,
|
OK: true,
|
||||||
}
|
}
|
||||||
resp.Output, err = trigger.Run()
|
resp.Output, err = trigger.Run(datapoints)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
we.Wrap(err).Log()
|
we.Wrap(err).Log()
|
||||||
httpError(w, err)
|
httpError(w, err)
|
||||||
@ -560,7 +463,6 @@ func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {
|
|||||||
w.Header().Add("Content-Type", "application/json")
|
w.Header().Add("Content-Type", "application/json")
|
||||||
w.Write(j)
|
w.Write(j)
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func pageConfiguration(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
func pageConfiguration(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||||
areas, err := AreaRetrieve()
|
areas, err := AreaRetrieve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
1
page.go
1
page.go
@ -52,6 +52,7 @@ func (p *Page) Render(w http.ResponseWriter) {
|
|||||||
"Data": p.Data,
|
"Data": p.Data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.Info("foo", "tmpl", tmpl)
|
||||||
err = tmpl.Execute(w, data)
|
err = tmpl.Execute(w, data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
|
105
problem.go
105
problem.go
@ -2,108 +2,19 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// External
|
// External
|
||||||
we "git.gibonuddevalla.se/go/wrappederror"
|
// we "git.gibonuddevalla.se/go/wrappederror"
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
"database/sql"
|
// "encoding/json"
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
type Problem struct {
|
type Problem struct {
|
||||||
ID int
|
ID int
|
||||||
Start time.Time
|
Name string
|
||||||
End sql.NullTime
|
SectionID int
|
||||||
Acknowledged bool
|
Expression string
|
||||||
TriggerID int `json:"trigger_id"`
|
DatapointNames []string
|
||||||
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(`
|
|
||||||
SELECT COUNT(id)
|
|
||||||
FROM problem
|
|
||||||
WHERE
|
|
||||||
trigger_id = $1 AND
|
|
||||||
"end" IS NULL
|
|
||||||
GROUP BY trigger_id
|
|
||||||
`,
|
|
||||||
trigger.ID,
|
|
||||||
)
|
|
||||||
var openProblems int
|
|
||||||
err = row.Scan(&openProblems)
|
|
||||||
if err != nil && err != sql.ErrNoRows {
|
|
||||||
err = we.Wrap(err).WithData(trigger.ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open up a new problem if no open exists.
|
|
||||||
if openProblems == 0 {
|
|
||||||
_, err = service.Db.Conn.Exec(`INSERT INTO problem(trigger_id) VALUES($1)`, trigger.ID)
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).WithData(trigger)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProblemClose(trigger Trigger) (err error) {
|
|
||||||
_, err = service.Db.Conn.Exec(`UPDATE problem SET "end"=NOW() WHERE trigger_id=$1 AND "end" IS NULL`, trigger.ID)
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).WithData(trigger)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
@ -1 +0,0 @@
|
|||||||
ALTER TABLE public."trigger" ADD CONSTRAINT trigger_sectionname_unique UNIQUE (section_id,"name");
|
|
@ -37,7 +37,7 @@ h2 {
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #fabd2f;
|
color: #3f9da1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
@ -70,31 +70,13 @@ button {
|
|||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
padding: 8px 32px;
|
padding: 8px 32px;
|
||||||
border: 1px solid #535353;
|
border: 1px solid #3a3a3a;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
button:focus {
|
button:focus {
|
||||||
background: #333;
|
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 {
|
#datapoints {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, min-content);
|
grid-template-columns: repeat(4, min-content);
|
||||||
@ -120,10 +102,10 @@ label {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.widgets .datapoints {
|
.widgets .datapoints {
|
||||||
|
font: "Roboto Mono", monospace;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
gap: 6px 8px;
|
gap: 6px 8px;
|
||||||
font-family: "Roboto Mono", monospace;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
.widgets .action {
|
.widgets .action {
|
||||||
|
@ -37,7 +37,7 @@ h2 {
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #fabd2f;
|
color: #3f9da1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
@ -70,31 +70,13 @@ button {
|
|||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
padding: 8px 32px;
|
padding: 8px 32px;
|
||||||
border: 1px solid #535353;
|
border: 1px solid #3a3a3a;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
button:focus {
|
button:focus {
|
||||||
background: #333;
|
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 {
|
#layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "menu content";
|
grid-template-areas: "menu content";
|
||||||
@ -155,18 +137,13 @@ label {
|
|||||||
#areas .area .section {
|
#areas .area .section {
|
||||||
margin: 8px 16px;
|
margin: 8px 16px;
|
||||||
}
|
}
|
||||||
#areas .area .section .create {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: min-content min-content;
|
|
||||||
grid-gap: 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
#areas .area .section .create .new {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
#areas .area .section > .name {
|
#areas .area .section > .name {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
#areas .area .section .triggers a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
#areas .area .section .triggers .trigger {
|
#areas .area .section .triggers .trigger {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
@ -178,11 +155,5 @@ label {
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
#areas .area .section .triggers .trigger .label {
|
#areas .area .section .triggers .trigger .label {
|
||||||
color: inherit;
|
color: #3f9da1;
|
||||||
}
|
|
||||||
dialog {
|
|
||||||
background: #202020;
|
|
||||||
border: 1px solid #606060;
|
|
||||||
color: #d5c4a1;
|
|
||||||
box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
}
|
||||||
|
@ -1,124 +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: "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;
|
|
||||||
}
|
|
@ -37,7 +37,7 @@ h2 {
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #fabd2f;
|
color: #3f9da1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
@ -70,28 +70,10 @@ button {
|
|||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
padding: 8px 32px;
|
padding: 8px 32px;
|
||||||
border: 1px solid #535353;
|
border: 1px solid #3a3a3a;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
button:focus {
|
button:focus {
|
||||||
background: #333;
|
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;
|
|
||||||
}
|
|
||||||
|
@ -37,7 +37,7 @@ h2 {
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
color: #fabd2f;
|
color: #3f9da1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
a:hover {
|
a:hover {
|
||||||
@ -70,31 +70,13 @@ button {
|
|||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
padding: 8px 32px;
|
padding: 8px 32px;
|
||||||
border: 1px solid #535353;
|
border: 1px solid #3a3a3a;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
button:focus {
|
button:focus {
|
||||||
background: #333;
|
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 {
|
.widgets {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="31.999987"
|
|
||||||
height="29.090721"
|
|
||||||
viewBox="0 0 8.4666629 7.6969202"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
|
||||||
sodipodi:docname="acknowledge-filled.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="1"
|
|
||||||
inkscape:cx="11.5"
|
|
||||||
inkscape:cy="10"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="2190"
|
|
||||||
inkscape:window-height="1404"
|
|
||||||
inkscape:window-x="1463"
|
|
||||||
inkscape:window-y="16"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:showpageshadow="true"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d6d6d6"
|
|
||||||
showborder="true" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-89.958296,-148.43122)">
|
|
||||||
<title
|
|
||||||
id="title1">thumb-up</title>
|
|
||||||
<path
|
|
||||||
d="m 98.424959,151.89488 c 0,-0.42719 -0.346361,-0.7697 -0.769692,-0.7697 h -2.432224 l 0.369452,-1.75874 c 0.0077,-0.0385 0.01149,-0.0809 0.01149,-0.1232 0,-0.15778 -0.06542,-0.30402 -0.169332,-0.40794 l -0.407941,-0.40408 -2.532284,2.53229 c -0.142399,0.1424 -0.227059,0.33482 -0.227059,0.54648 v 3.84845 a 0.76969147,0.76969147 0 0 0 0.769691,0.7697 h 3.463611 c 0.319422,0 0.592664,-0.19242 0.708117,-0.46951 l 1.162234,-2.71317 c 0.03463,-0.0884 0.05388,-0.18087 0.05388,-0.28093 v -0.7697 m -8.466606,4.23331 h 1.539382 v -4.61815 h -1.539382 z"
|
|
||||||
id="path1"
|
|
||||||
style="stroke-width:0.384845;fill:#fb4934;fill-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.4 KiB |
@ -1,69 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
|
||||||
|
|
||||||
<svg
|
|
||||||
width="31.999987"
|
|
||||||
height="29.090721"
|
|
||||||
viewBox="0 0 8.4666629 7.6969202"
|
|
||||||
version="1.1"
|
|
||||||
id="svg8"
|
|
||||||
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
|
||||||
sodipodi:docname="acknowledge-outline.svg"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
|
||||||
xmlns:cc="http://creativecommons.org/ns#"
|
|
||||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
|
||||||
<defs
|
|
||||||
id="defs2" />
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="base"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#666666"
|
|
||||||
borderopacity="1.0"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pageshadow="2"
|
|
||||||
inkscape:zoom="1"
|
|
||||||
inkscape:cx="11.5"
|
|
||||||
inkscape:cy="10"
|
|
||||||
inkscape:document-units="px"
|
|
||||||
inkscape:current-layer="layer1"
|
|
||||||
showgrid="false"
|
|
||||||
units="px"
|
|
||||||
inkscape:window-width="2190"
|
|
||||||
inkscape:window-height="1404"
|
|
||||||
inkscape:window-x="1463"
|
|
||||||
inkscape:window-y="16"
|
|
||||||
inkscape:window-maximized="0"
|
|
||||||
inkscape:showpageshadow="true"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d6d6d6"
|
|
||||||
showborder="true" />
|
|
||||||
<metadata
|
|
||||||
id="metadata5">
|
|
||||||
<rdf:RDF>
|
|
||||||
<cc:Work
|
|
||||||
rdf:about="">
|
|
||||||
<dc:format>image/svg+xml</dc:format>
|
|
||||||
<dc:type
|
|
||||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
|
||||||
</cc:Work>
|
|
||||||
</rdf:RDF>
|
|
||||||
</metadata>
|
|
||||||
<g
|
|
||||||
inkscape:label="Layer 1"
|
|
||||||
inkscape:groupmode="layer"
|
|
||||||
id="layer1"
|
|
||||||
transform="translate(-89.958296,-148.43122)">
|
|
||||||
<title
|
|
||||||
id="title1">thumb-up</title>
|
|
||||||
<title
|
|
||||||
id="title1-3">thumb-up-outline</title>
|
|
||||||
<path
|
|
||||||
d="m 91.497681,151.50999 v 4.61815 h -1.539385 v -4.61815 h 1.539385 m 1.539383,4.61815 a 0.76969186,0.76969186 0 0 1 -0.769692,-0.76969 v -3.84846 c 0,-0.21166 0.08467,-0.40408 0.22706,-0.54263 l 2.532286,-2.53614 0.407936,0.40794 c 0.103912,0.10385 0.169333,0.24631 0.169333,0.40408 l -0.01149,0.1232 -0.365604,1.75875 h 2.428378 c 0.42718,0 0.769693,0.34636 0.769693,0.76969 v 0.76969 c 0,0.10008 -0.01924,0.19242 -0.05388,0.28095 l -1.162236,2.71316 c -0.115446,0.27709 -0.388694,0.46951 -0.708116,0.46951 h -3.463613 m 0,-0.7697 h 3.47516 l 1.142991,-2.69392 v -0.76969 h -3.382795 l 0.434876,-2.04738 -1.670232,1.67408 z"
|
|
||||||
id="path1"
|
|
||||||
style="stroke-width:0.384845;fill:#fb4934;fill-opacity:1" />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 2.6 KiB |
@ -1,23 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,35 +1,17 @@
|
|||||||
export class UI {
|
export class UI {
|
||||||
constructor() {//{{{
|
constructor() {
|
||||||
document.getElementById('button-run').
|
document.getElementById('button-run').
|
||||||
addEventListener('click', evt=>evt.preventDefault())
|
addEventListener('click', evt=>evt.preventDefault())
|
||||||
|
|
||||||
document.addEventListener('keydown', evt=>this.keyHandler(evt))
|
document.addEventListener('keydown', evt=>this.keyHandler(evt))
|
||||||
|
}
|
||||||
document.querySelector('input[name="name"]').focus()
|
setTrigger(t) {
|
||||||
|
|
||||||
this.datapoints = []
|
|
||||||
}//}}}
|
|
||||||
render() {//{{{
|
|
||||||
document.querySelectorAll('.datapoints .datapoint').forEach(el => el.remove());
|
|
||||||
|
|
||||||
const datapoints = document.querySelector('.datapoints')
|
|
||||||
|
|
||||||
let html = Object.keys(this.trigger.datapoints).sort().map(dpName => {
|
|
||||||
const dp = this.trigger.datapoints[dpName]
|
|
||||||
return `
|
|
||||||
<div class="datapoint name"><b>${dp.Name}</b></div>
|
|
||||||
<div class="datapoint value">${dp.LastDatapointValue.TemplateValue}</div>
|
|
||||||
`
|
|
||||||
}).join('')
|
|
||||||
datapoints.innerHTML += html
|
|
||||||
}//}}}
|
|
||||||
setTrigger(t) {//{{{
|
|
||||||
this.trigger = t
|
this.trigger = t
|
||||||
}//}}}
|
}
|
||||||
run() {//{{{
|
run() {
|
||||||
this.trigger.run()
|
this.trigger.run()
|
||||||
}//}}}
|
}
|
||||||
keyHandler(evt) {//{{{
|
keyHandler(evt) {
|
||||||
if (!(evt.altKey && evt.shiftKey))
|
if (!(evt.altKey && evt.shiftKey))
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -42,72 +24,18 @@ export class UI {
|
|||||||
break
|
break
|
||||||
|
|
||||||
case 'S':
|
case 'S':
|
||||||
this.update()
|
document.getElementById('form-trigger').submit()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}//}}}
|
|
||||||
addDatapoint() {//{{{
|
|
||||||
const dlg = document.getElementById('dlg-datapoints')
|
|
||||||
const datalist = document.getElementById('list-datapoints')
|
|
||||||
dlg.showModal()
|
|
||||||
|
|
||||||
fetch('/datapoints?format=json')
|
|
||||||
.then(data => data.json())
|
|
||||||
.then(json => {
|
|
||||||
this.datapoints = json
|
|
||||||
|
|
||||||
let html = ''
|
|
||||||
this.datapoints.forEach(dp => {
|
|
||||||
html += `<option value="${dp.Name}">`
|
|
||||||
})
|
|
||||||
datalist.innerHTML = html
|
|
||||||
})
|
|
||||||
.catch(err => alert(err))
|
|
||||||
}//}}}
|
|
||||||
chooseDatapoint() {//{{{
|
|
||||||
const dlg = document.getElementById('dlg-datapoints')
|
|
||||||
const datapoint = document.getElementById('datapoint').value
|
|
||||||
const dp = this.datapoints.find(dp => dp.Name == datapoint)
|
|
||||||
|
|
||||||
if (dp === undefined) {
|
|
||||||
alert('Invalid datapoint')
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.trigger.addDatapoint(dp)
|
|
||||||
dlg.close()
|
|
||||||
this.render()
|
|
||||||
}//}}}
|
|
||||||
update() {//{{{
|
|
||||||
const form = document.getElementById('form-trigger')
|
|
||||||
var formData = new FormData(form)
|
|
||||||
Object.keys(this.trigger.datapoints).forEach(name => formData.append("datapoints[]", name))
|
|
||||||
fetch(form.action, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
})
|
|
||||||
.then(resp => {
|
|
||||||
if (resp.redirected) {
|
|
||||||
location.href = resp.url
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return resp.json()
|
|
||||||
})
|
|
||||||
.then(json => {
|
|
||||||
if (json)
|
|
||||||
alert(json.Error)
|
|
||||||
})
|
|
||||||
.catch(err => alert(err))
|
|
||||||
}//}}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Trigger {
|
export class Trigger {
|
||||||
constructor(id, name, datapoints) {//{{{
|
constructor(id, name) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.name = name
|
this.name = name
|
||||||
this.datapoints = datapoints
|
}
|
||||||
}//}}}
|
run() {
|
||||||
run() {//{{{
|
|
||||||
const result = document.getElementById('run-result')
|
const result = document.getElementById('run-result')
|
||||||
const classes = result.classList
|
const classes = result.classList
|
||||||
const expr = document.getElementById('expr').value
|
const expr = document.getElementById('expr').value
|
||||||
@ -131,8 +59,5 @@ export class Trigger {
|
|||||||
result.innerText = json.Output
|
result.innerText = json.Output
|
||||||
})
|
})
|
||||||
.catch(err => alert(err))
|
.catch(err => alert(err))
|
||||||
}//}}}
|
}
|
||||||
addDatapoint(dp) {//{{{
|
|
||||||
this.datapoints[dp.Name] = dp
|
|
||||||
}//}}}
|
|
||||||
}
|
}
|
||||||
|
@ -29,10 +29,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.datapoints {
|
.datapoints {
|
||||||
|
font: "Roboto Mono", monospace;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
gap: 6px 8px;
|
gap: 6px 8px;
|
||||||
font-family: "Roboto Mono", monospace;
|
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,22 +71,17 @@
|
|||||||
.section {
|
.section {
|
||||||
margin: 8px 16px;
|
margin: 8px 16px;
|
||||||
|
|
||||||
.create {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: min-content min-content;
|
|
||||||
grid-gap: 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
.new {
|
|
||||||
font-weight: @bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&>.name {
|
&>.name {
|
||||||
font-weight: @bold;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.triggers {
|
.triggers {
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.trigger {
|
.trigger {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
@ -99,17 +94,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: inherit;
|
color: @color4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dialog {
|
|
||||||
background: @bg2;
|
|
||||||
border: 1px solid lighten(@bg2, 25%);
|
|
||||||
color: @text1;
|
|
||||||
box-shadow: 10px 10px 15px 0px rgba(0, 0, 0, 0.25);
|
|
||||||
}
|
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
@ -14,17 +14,11 @@
|
|||||||
|
|
||||||
@bold: 500;
|
@bold: 500;
|
||||||
|
|
||||||
.lighterOrDarker(@color, @amount) {
|
|
||||||
@result: lighten(@color, @amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*, *:before, *:after {
|
||||||
*:before,
|
|
||||||
*:after {
|
|
||||||
box-sizing: inherit;
|
box-sizing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +44,7 @@ body {
|
|||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
h1, h2 {
|
||||||
h2 {
|
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
@ -66,7 +59,7 @@ h2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: @color2;
|
color: @color4;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -90,9 +83,7 @@ b {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"],
|
input[type="text"], textarea, select {
|
||||||
textarea,
|
|
||||||
select {
|
|
||||||
font-family: "Roboto Mono", monospace;
|
font-family: "Roboto Mono", monospace;
|
||||||
background: @bg2;
|
background: @bg2;
|
||||||
color: @text1;
|
color: @text1;
|
||||||
@ -105,7 +96,7 @@ button {
|
|||||||
background: @bg2;
|
background: @bg2;
|
||||||
color: @text1;
|
color: @text1;
|
||||||
padding: 8px 32px;
|
padding: 8px 32px;
|
||||||
border: 1px solid lighten(@bg2, 20%);
|
border: 1px solid lighten(@bg2, 10%);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
|
|
||||||
@ -113,26 +104,3 @@ button {
|
|||||||
background: @bg3;
|
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;
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
@import "theme.less";
|
@import "theme.less";
|
||||||
|
|
||||||
#dlg-datapoints {
|
|
||||||
}
|
|
||||||
|
|
||||||
.widgets {
|
.widgets {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
|
87
trigger.go
87
trigger.go
@ -4,7 +4,6 @@ import (
|
|||||||
// External
|
// External
|
||||||
we "git.gibonuddevalla.se/go/wrappederror"
|
we "git.gibonuddevalla.se/go/wrappederror"
|
||||||
"github.com/expr-lang/expr"
|
"github.com/expr-lang/expr"
|
||||||
"github.com/lib/pq"
|
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@ -20,6 +19,9 @@ type Trigger struct {
|
|||||||
Datapoints []string
|
Datapoints []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Foo() {
|
||||||
|
}
|
||||||
|
|
||||||
func TriggersRetrieve() (areas []Area, err error) { // {{{
|
func TriggersRetrieve() (areas []Area, err error) { // {{{
|
||||||
areas = []Area{}
|
areas = []Area{}
|
||||||
|
|
||||||
@ -71,32 +73,6 @@ func TriggersRetrieve() (areas []Area, err error) { // {{{
|
|||||||
|
|
||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
func TriggersRetrieveByDatapoint(datapointName string) (triggers []Trigger, err error) { // {{{
|
|
||||||
triggers = []Trigger{}
|
|
||||||
row := service.Db.Conn.QueryRow(`
|
|
||||||
SELECT jsonb_agg(t.*)
|
|
||||||
FROM public."trigger" t
|
|
||||||
WHERE
|
|
||||||
datapoints @> $1
|
|
||||||
`,
|
|
||||||
fmt.Sprintf(`["%s"]`, datapointName),
|
|
||||||
)
|
|
||||||
|
|
||||||
var data []byte
|
|
||||||
err = row.Scan(&data)
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).WithData(datapointName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(data, &triggers)
|
|
||||||
if err != nil {
|
|
||||||
err = we.Wrap(err).WithData(datapointName)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
} // }}}
|
|
||||||
func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{
|
func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{
|
||||||
row := service.Db.Conn.QueryRow(`SELECT to_jsonb(t.*) FROM "trigger" t WHERE id=$1`, id)
|
row := service.Db.Conn.QueryRow(`SELECT to_jsonb(t.*) FROM "trigger" t WHERE id=$1`, id)
|
||||||
var jsonData []byte
|
var jsonData []byte
|
||||||
@ -109,7 +85,7 @@ func TriggerRetrieve(id int) (trigger Trigger, err error) { // {{{
|
|||||||
err = json.Unmarshal(jsonData, &trigger)
|
err = json.Unmarshal(jsonData, &trigger)
|
||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
func (t *Trigger) Validate() (ok bool, err error) { // {{{
|
func (t *Trigger) Validate() (ok bool, err error) {
|
||||||
if strings.TrimSpace(t.Name) == "" {
|
if strings.TrimSpace(t.Name) == "" {
|
||||||
err = fmt.Errorf("Name can't be empty")
|
err = fmt.Errorf("Name can't be empty")
|
||||||
return
|
return
|
||||||
@ -121,73 +97,33 @@ func (t *Trigger) Validate() (ok bool, err error) { // {{{
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
} // }}}
|
}
|
||||||
func (t *Trigger) Update() (err error) { // {{{
|
func (t *Trigger) Update() (err error) {
|
||||||
var ok bool
|
var ok bool
|
||||||
if ok, err = t.Validate(); !ok {
|
if ok, err = t.Validate(); !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Datapoints == nil {
|
logger.Info("FOO", "trigger", t)
|
||||||
t.Datapoints = []string{}
|
|
||||||
}
|
|
||||||
jsonDatapoints, _ := json.Marshal(t.Datapoints)
|
|
||||||
if t.ID == 0 {
|
|
||||||
_, err = service.Db.Conn.Exec(`
|
|
||||||
INSERT INTO "trigger"(name, section_id, expression, datapoints)
|
|
||||||
VALUES($1, $2, $3, $4)
|
|
||||||
`,
|
|
||||||
t.Name,
|
|
||||||
t.SectionID,
|
|
||||||
t.Expression,
|
|
||||||
jsonDatapoints,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
_, err = service.Db.Conn.Exec(`
|
_, err = service.Db.Conn.Exec(`
|
||||||
UPDATE "trigger"
|
UPDATE "trigger"
|
||||||
SET
|
SET
|
||||||
name=$2,
|
name=$2,
|
||||||
expression=$3,
|
expression=$3
|
||||||
datapoints=$4
|
|
||||||
WHERE
|
WHERE
|
||||||
id=$1
|
id=$1
|
||||||
`,
|
`,
|
||||||
t.ID,
|
t.ID,
|
||||||
t.Name,
|
t.Name,
|
||||||
t.Expression,
|
t.Expression,
|
||||||
jsonDatapoints,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if pqErr, ok := err.(*pq.Error); ok {
|
|
||||||
err = we.Wrap(err).WithData(
|
|
||||||
struct {
|
|
||||||
Trigger *Trigger
|
|
||||||
PostgresCode pq.ErrorCode
|
|
||||||
PostgresMsg string
|
|
||||||
}{
|
|
||||||
t,
|
|
||||||
pqErr.Code,
|
|
||||||
pqErr.Code.Name(),
|
|
||||||
})
|
|
||||||
} else if err != nil {
|
|
||||||
err = we.Wrap(err).WithData(t)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
} // }}}
|
|
||||||
|
|
||||||
func (t *Trigger) Run() (output any, err error) { // {{{
|
|
||||||
datapoints := make(map[string]Datapoint)
|
|
||||||
for _, dpname := range t.Datapoints {
|
|
||||||
var dp Datapoint
|
|
||||||
dp, err = DatapointRetrieve(0, dpname)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err)
|
err = we.Wrap(err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
datapoints[dpname] = dp
|
|
||||||
}
|
|
||||||
|
|
||||||
|
func (t *Trigger) Run(datapoints map[string]Datapoint) (output any, err error) {
|
||||||
env := make(map[string]any)
|
env := make(map[string]any)
|
||||||
for dpName, dp := range datapoints {
|
for dpName, dp := range datapoints {
|
||||||
env[dpName] = dp.LastDatapointValue.Value()
|
env[dpName] = dp.LastDatapointValue.Value()
|
||||||
@ -202,5 +138,6 @@ func (t *Trigger) Run() (output any, err error) { // {{{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
} // }}}
|
}
|
||||||
|
@ -12,7 +12,6 @@
|
|||||||
<div class="header">Value</div>
|
<div class="header">Value</div>
|
||||||
|
|
||||||
{{ range .Data.Datapoints }}
|
{{ range .Data.Datapoints }}
|
||||||
<div class="line"></div>
|
|
||||||
<div class="name"><a href="/datapoint/edit/{{ .ID }}">{{ .Name }}</a></div>
|
<div class="name"><a href="/datapoint/edit/{{ .ID }}">{{ .Name }}</a></div>
|
||||||
<div class="datatype">{{ .Datatype }}</div>
|
<div class="datatype">{{ .Datatype }}</div>
|
||||||
<div class="last-value">{{ format_time .LastValue }}</div>
|
<div class="last-value">{{ format_time .LastValue }}</div>
|
||||||
|
@ -1,56 +1,6 @@
|
|||||||
{{ define "page" }}
|
{{ define "page" }}
|
||||||
{{ $version := .VERSION }}
|
|
||||||
<script type="module" defer>
|
|
||||||
import {UI} from "/js/{{ .VERSION }}/problems.mjs"
|
|
||||||
|
|
||||||
window._ui = new UI()
|
|
||||||
</script>
|
|
||||||
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/problems.css">
|
|
||||||
|
|
||||||
{{ block "page_label" . }}{{end}}
|
{{ block "page_label" . }}{{end}}
|
||||||
|
|
||||||
<div id="problems-list">
|
|
||||||
<div style="grid-column: 1/-1; margin-top: 32px;"><h2>Current</h2></div>
|
|
||||||
|
|
||||||
<div class="header">Trigger</div>
|
|
||||||
<div class="header">Area</div>
|
|
||||||
<div class="header">Section</div>
|
|
||||||
<div class="header">Since</div>
|
|
||||||
{{ range .Data.Problems }}
|
|
||||||
{{ if .Acknowledged }}
|
|
||||||
{{ continue }}
|
|
||||||
{{ end }}
|
|
||||||
<div class="line"></div>
|
|
||||||
<div class="trigger"><a href="/trigger/edit/{{ .TriggerID }}">{{ .TriggerName }}</a></div>
|
|
||||||
<div class="area">{{ .AreaName }}</div>
|
|
||||||
<div class="section">{{ .SectionName }}</div>
|
|
||||||
<div class="start">{{ format_time .Start }}</div>
|
|
||||||
<div class="acknowledge"><a href="/problem/acknowledge/{{ .ID }}"><img src="/images/{{ $version }}/acknowledge-filled.svg"></a></div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="checkbox" id="show-acked" onclick="_ui.toggleAcknowledged(event)"> <label for="show-acked">Show acknowledged</label>
|
|
||||||
|
|
||||||
<div id="acknowledged-list" class="hidden">
|
|
||||||
<div style="grid-column: 1/-1; margin-top: 32px;"><h2>Acknowledged</h2></div>
|
|
||||||
<div class="header">Trigger</div>
|
|
||||||
<div class="header">Area</div>
|
|
||||||
<div class="header">Section</div>
|
|
||||||
<div class="header">Since</div>
|
|
||||||
|
|
||||||
{{ range .Data.Problems }}
|
|
||||||
{{ if not .Acknowledged }}
|
|
||||||
{{ continue }}
|
|
||||||
{{ end }}
|
|
||||||
<div class="line"></div>
|
|
||||||
<div class="trigger"><a href="/trigger/edit/{{ .TriggerID }}">{{ .TriggerName }}</a></div>
|
|
||||||
<div class="area">{{ .AreaName }}</div>
|
|
||||||
<div class="section">{{ .SectionName }}</div>
|
|
||||||
<div class="start">{{ format_time .Start }}</div>
|
|
||||||
<div class="acknowledge"><a href="/problem/unacknowledge/{{ .ID }}"><img src="/images/{{ $version }}/acknowledge-outline.svg"></a></div>
|
|
||||||
{{ end }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="areas">
|
<div id="areas">
|
||||||
{{ range .Data.Areas }}
|
{{ range .Data.Areas }}
|
||||||
<div class="area">
|
<div class="area">
|
||||||
|
@ -6,31 +6,24 @@
|
|||||||
let trigger = new Trigger(
|
let trigger = new Trigger(
|
||||||
{{ .Data.Trigger.ID }},
|
{{ .Data.Trigger.ID }},
|
||||||
'{{ .Data.Trigger.Name }}',
|
'{{ .Data.Trigger.Name }}',
|
||||||
{{ .Data.Datapoints }},
|
|
||||||
)
|
)
|
||||||
_ui.setTrigger(trigger)
|
_ui.setTrigger(trigger)
|
||||||
_ui.render()
|
|
||||||
</script>
|
</script>
|
||||||
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/trigger_edit.css">
|
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/trigger_edit.css">
|
||||||
|
|
||||||
{{ block "page_label" . }}{{end}}
|
{{ block "page_label" . }}{{end}}
|
||||||
|
|
||||||
<dialog id="dlg-datapoints">
|
|
||||||
<input list="list-datapoints" id="datapoint">
|
|
||||||
<datalist id="list-datapoints"></datalist>
|
|
||||||
<button onclick="_ui.chooseDatapoint()">OK</button>
|
|
||||||
</dialog>
|
|
||||||
|
|
||||||
<form id="form-trigger" action="/trigger/update/{{ .Data.Trigger.ID }}" method="post">
|
<form id="form-trigger" action="/trigger/update/{{ .Data.Trigger.ID }}" method="post">
|
||||||
<input type="hidden" name="sectionID" value="{{ .Data.Trigger.SectionID }}">
|
|
||||||
<div id="widgets" class="widgets">
|
<div id="widgets" class="widgets">
|
||||||
<div class="label">Name</div>
|
<div class="label">Name</div>
|
||||||
<div><input type="text" name="name" value="{{ .Data.Trigger.Name }}"></div>
|
<div><input type="text" name="name" value="{{ .Data.Trigger.Name }}"></div>
|
||||||
|
|
||||||
<div class="label">Datapoints</div>
|
<div class="label">Datapoints</div>
|
||||||
<div class="datapoints" style="margin-top: 4px">
|
<div class="datapoints" style="margin-top: 4px">
|
||||||
<div><a onclick="_ui.addDatapoint()">Add</a></div>
|
{{ range .Data.Datapoints }}
|
||||||
<div></div>
|
<div class="datapoint name"><b>{{ .Name }}</b></div>
|
||||||
|
<div class="datapoint value">{{ .LastDatapointValue.Value }}</div>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="label">Expression</div>
|
<div class="label">Expression</div>
|
||||||
@ -38,11 +31,10 @@
|
|||||||
|
|
||||||
<div></div>
|
<div></div>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<button id="button-update" onclick="_ui.update(); return false">{{ if eq .Data.Trigger.ID 0 }}Create{{ else }}Update{{ end }}</button>
|
<button id="button-update">Update</button>
|
||||||
<button id="button-run" onclick="window._ui.run(); return false">Test</button>
|
<button id="button-run" onclick="window._ui.run(); return false">Test</button>
|
||||||
<div id="run-result"></div>
|
<div id="run-result"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -9,10 +9,7 @@
|
|||||||
<div class="name">{{ .Name }}</div>
|
<div class="name">{{ .Name }}</div>
|
||||||
{{ range .SortedSections }}
|
{{ range .SortedSections }}
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="create">
|
|
||||||
<div class="name">{{ .Name }}</div>
|
<div class="name">{{ .Name }}</div>
|
||||||
<div class="new"><a href="/trigger/edit/0/{{ .ID }}">+</a></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="triggers">
|
<div class="triggers">
|
||||||
{{ range .SortedTriggers }}
|
{{ range .SortedTriggers }}
|
||||||
|
Loading…
Reference in New Issue
Block a user