diff --git a/datapoint.go b/datapoint.go index 845f744..3672033 100644 --- a/datapoint.go +++ b/datapoint.go @@ -3,9 +3,12 @@ package main import ( // External we "git.gibonuddevalla.se/go/wrappederror" + "github.com/jmoiron/sqlx" // Standard "database/sql" + "errors" + "strings" "time" ) @@ -20,7 +23,6 @@ const ( type Datapoint struct { ID int Name string - SectionID int `db:"section_id"` Datatype DatapointType LastValue time.Time `db:"last_value"` DatapointValueJSON []byte `db:"datapoint_value_json"` @@ -36,20 +38,53 @@ type DatapointValue struct { ValueDateTime sql.NullTime `db:"value_datetime"` } -func (dp DatapointValue) Value() any { +func (dp DatapointValue) Value() any { // {{{ if dp.ValueInt.Valid { return dp.ValueInt.Int64 } + if dp.ValueString.Valid { return dp.ValueString.String } + if dp.ValueDateTime.Valid { return dp.ValueDateTime.Time } + return nil +} // }}} +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) { + name := strings.TrimSpace(dp.Name) + if name == "" { + err = errors.New("Name can't be empty") + return + } + + if dp.ID == 0 { + _, err = service.Db.Conn.Exec( + `INSERT INTO datapoint(name, datatype) VALUES($1, $2)`, + name, + dp.Datatype, + ) + } else { + _, err = service.Db.Conn.Exec( + `UPDATE datapoint SET name=$2, datatype=$3 WHERE id=$1`, + dp.ID, + name, + dp.Datatype, + ) + } + + 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) var dpID int @@ -81,13 +116,94 @@ func DatapointAdd[T any](name string, value T) (err error) { } return -} +} // }}} -func DatapointRetrieve(name string) (dp Datapoint, err error) { - row := service.Db.Conn.QueryRowx( - `SELECT * FROM datapoint dp WHERE dp.name = $1`, - name, - ) +func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{ + dps = []Datapoint{} + var rows *sqlx.Rows + rows, err = service.Db.Conn.Queryx(` + SELECT + dp.id, + dp.name, + dp.datatype, + dp.last_value, + + dpv.id AS v_id, + dpv.ts, + dpv.value_int, + dpv.value_string, + dpv.value_datetime + + FROM public.datapoint dp + LEFT JOIN ( + SELECT + *, + row_number() OVER (PARTITION BY "datapoint_id" ORDER BY ts DESC) AS rn + FROM datapoint_value + ) dpv ON dpv.datapoint_id = dp.id AND rn = 1 + ORDER BY + dp.name ASC + `) + if err != nil { + err = we.Wrap(err) + } + defer rows.Close() + + type DbRes struct { + ID int + Name string + Datatype DatapointType + LastValue time.Time `db:"last_value"` + + VID sql.NullInt64 `db:"v_id"` + Ts sql.NullTime + ValueInt sql.NullInt64 `db:"value_int"` + ValueString sql.NullString `db:"value_string"` + ValueDateTime sql.NullTime `db:"value_datetime"` + } + + for rows.Next() { + dp := Datapoint{} + dpv := DatapointValue{} + res := DbRes{} + err = rows.StructScan(&res) + if err != nil { + err = we.Wrap(err) + return + } + + dp.ID = res.ID + dp.Name = res.Name + dp.Datatype = res.Datatype + dp.LastValue = res.LastValue + + if res.VID.Valid { + dpv.ID = int(res.VID.Int64) + dpv.Ts = res.Ts.Time + dpv.ValueInt = res.ValueInt + dpv.ValueString = res.ValueString + dpv.ValueDateTime = res.ValueDateTime + + dp.LastDatapointValue = dpv + } + + dps = append(dps, dp) + } + return +} // }}} +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` + param = id + dp.ID = id + } else { + query = `SELECT * FROM datapoint WHERE name = $1` + param = name + } + + row := service.Db.Conn.QueryRowx(query, param) err = row.StructScan(&dp) if err != nil { err = we.Wrap(err).WithData(name) @@ -104,9 +220,15 @@ func DatapointRetrieve(name string) (dp Datapoint, err error) { dp.ID, ) err = row.StructScan(&dp.LastDatapointValue) + if err == sql.ErrNoRows { + err = nil + return + } + if err != nil { err = we.Wrap(err).WithData(dp.ID) return } + return -} +} // }}} diff --git a/main.go b/main.go index a129a72..87fab04 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "path" "sort" "strconv" + "time" ) const VERSION = "v1" @@ -88,8 +89,23 @@ func main() { // {{{ service.SetDatabase(sqlProvider) service.SetStaticFS(staticFS, "static") service.SetStaticDirectory("static", flagDev) + service.InitDatabaseConnection() + err = service.Db.Connect() + if err != nil { + 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("/datapoints", false, false, pageDatapoints) + service.Register("/datapoint/edit/{id}", false, false, pageDatapointEdit) + service.Register("/datapoint/update/{id}", false, false, pageDatapointUpdate) service.Register("/triggers", false, false, pageTriggers) service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit) service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate) @@ -187,21 +203,26 @@ func getPage(layout, page string) (tmpl *template.Template, err error) { // {{{ return tmpl, nil } + funcMap := template.FuncMap{ + "format_time": func(t time.Time) string { + return t.Local().Format("2006-01-02 15:04:05") + }, + } + filenames := []string{layoutFilename, pageFilename} filenames = append(filenames, componentFilenames...) logger.Info("template", "op", "parse", "layout", layout, "page", page, "filenames", filenames) if flagDev { - parsedTemplates[page], err = template.ParseFS(os.DirFS("."), filenames...) + tmpl, err = template.New("main.gotmpl").Funcs(funcMap).ParseFS(os.DirFS("."), filenames...) } else { - parsedTemplates[page], err = template.ParseFS(viewFS, filenames...) + tmpl, err = template.New("main.gotmpl").ParseFS(viewFS, filenames...) } - tmpl = parsedTemplates[page] - if err != nil { err = we.Wrap(err).Log() return } + parsedTemplates[page] = tmpl return } // }}} @@ -237,6 +258,80 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ page.Render(w) return } // }}} +func pageDatapoints(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{ + page := Page{ + LAYOUT: "main", + PAGE: "datapoints", + } + + datapoints, err := DatapointsRetrieve() + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + logger.Info("FOO", "dps", datapoints) + + page.Data = map[string]any{ + "Datapoints": datapoints, + } + page.Render(w) + return +} // }}} +func pageDatapointEdit(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 + } + + var datapoint Datapoint + if id == 0 { + datapoint.Name = "new_datapoint" + datapoint.Datatype = "INT" + } else { + datapoint, err = DatapointRetrieve(id, "") + if err != nil { + httpError(w, we.Wrap(err).Log()) + return + } + } + + page := Page{ + LAYOUT: "main", + PAGE: "datapoint_edit", + MENU: "datapoints", + Icon: "datapoints", + Label: "Datapoint", + } + + page.Data = map[string]any{ + "Datapoint": datapoint, + } + page.Render(w) + return +} // }}} +func pageDatapointUpdate(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 + } + + var dp Datapoint + dp.ID = id + dp.Name = r.FormValue("name") + dp.Datatype = DatapointType(r.FormValue("datatype")) + err = dp.Update() + 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() if err != nil { @@ -275,7 +370,7 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { // datapoints := make(map[string]Datapoint) for _, dpname := range trigger.Datapoints { - dp, err := DatapointRetrieve(dpname) + dp, err := DatapointRetrieve(0, dpname) if err != nil { httpError(w, we.Wrap(err).Log()) return @@ -320,7 +415,7 @@ func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { / return } - w.Header().Add("Location", fmt.Sprintf("/trigger/edit/%d", id)) + w.Header().Add("Location", "/triggers") w.WriteHeader(302) } // }}} func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ @@ -343,7 +438,7 @@ func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // { datapoints := make(map[string]Datapoint) for _, dpname := range trigger.Datapoints { - dp, err := DatapointRetrieve(dpname) + dp, err := DatapointRetrieve(0, dpname) if err != nil { httpError(w, we.Wrap(err).Log()) return diff --git a/page.go b/page.go index 2465645..c6e22d4 100644 --- a/page.go +++ b/page.go @@ -52,6 +52,7 @@ func (p *Page) Render(w http.ResponseWriter) { "Data": p.Data, } + logger.Info("foo", "tmpl", tmpl) err = tmpl.Execute(w, data) if err != nil { httpError(w, we.Wrap(err).Log()) diff --git a/sql/00006.sql b/sql/00006.sql new file mode 100644 index 0000000..658a8a9 --- /dev/null +++ b/sql/00006.sql @@ -0,0 +1,9 @@ +CREATE TABLE public.problem ( + id serial8 NOT NULL, + trigger_id int4 NOT NULL, + "start" timestamptz DEFAULT now() NOT NULL, + "end" timestamptz NULL, + acknowledged bool DEFAULT false NOT NULL, + CONSTRAINT problem_pk PRIMARY KEY (id), + CONSTRAINT problem_trigger_fk FOREIGN KEY (trigger_id) REFERENCES public."trigger"(id) ON DELETE CASCADE ON UPDATE CASCADE +); diff --git a/sql/00007.sql b/sql/00007.sql new file mode 100644 index 0000000..d8db9fd --- /dev/null +++ b/sql/00007.sql @@ -0,0 +1 @@ +ALTER TABLE datapoint DROP COLUMN section_id; diff --git a/static/css/datapoints.css b/static/css/datapoints.css new file mode 100644 index 0000000..dddb99b --- /dev/null +++ b/static/css/datapoints.css @@ -0,0 +1,115 @@ +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: #3f9da1; + 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 #3a3a3a; + font-size: 1em; + height: 3em; +} +button:focus { + background: #333; +} +#datapoints { + display: grid; + grid-template-columns: repeat(4, min-content); + grid-gap: 8px 16px; + margin-top: 16px; +} +#datapoints .header { + font-weight: 500; +} +#datapoints div { + white-space: nowrap; +} +.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 1fr; + gap: 6px 8px; + margin-bottom: 8px; +} +.widgets .action { + display: grid; + grid-template-columns: min-content min-content; + grid-gap: 8px; +} diff --git a/static/css/main.css b/static/css/main.css index 6cf8aa2..f5b869e 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -20,6 +20,7 @@ body { body { background: #282828; font-family: "Roboto", sans-serif; + font-weight: 300; color: #d5c4a1; font-size: 11pt; } @@ -35,6 +36,16 @@ h1 { h2 { font-size: 1.25em; } +a { + color: #3f9da1; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +b { + font-weight: 500; +} .roboto-light { font-family: "Roboto", sans-serif; font-weight: 300; @@ -46,7 +57,8 @@ h2 { font-style: normal; } input[type="text"], -textarea { +textarea, +select { font-family: "Roboto Mono", monospace; background: #202020; color: #d5c4a1; @@ -62,6 +74,9 @@ button { font-size: 1em; height: 3em; } +button:focus { + background: #333; +} #layout { display: grid; grid-template-areas: "menu content"; @@ -94,6 +109,7 @@ button { margin-bottom: 32px; } #page .page-label div { + font-weight: 500; font-size: 1.5em; color: #fb4934; } @@ -113,7 +129,7 @@ button { #areas .area > .name { background: #fb4934; color: #fff; - font-weight: bold; + font-weight: 500; padding: 4px 16px; border-top-left-radius: 4px; border-top-right-radius: 4px; @@ -122,7 +138,7 @@ button { margin: 8px 16px; } #areas .area .section > .name { - font-weight: bold; + font-weight: 500; } #areas .area .section .triggers a { color: inherit; @@ -139,5 +155,5 @@ button { height: 16px; } #areas .area .section .triggers .trigger .label { - color: #f7edd7; + color: #3f9da1; } diff --git a/static/css/theme.css b/static/css/theme.css index 3d76859..6191e90 100644 --- a/static/css/theme.css +++ b/static/css/theme.css @@ -20,6 +20,7 @@ body { body { background: #282828; font-family: "Roboto", sans-serif; + font-weight: 300; color: #d5c4a1; font-size: 11pt; } @@ -35,6 +36,16 @@ h1 { h2 { font-size: 1.25em; } +a { + color: #3f9da1; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +b { + font-weight: 500; +} .roboto-light { font-family: "Roboto", sans-serif; font-weight: 300; @@ -46,7 +57,8 @@ h2 { font-style: normal; } input[type="text"], -textarea { +textarea, +select { font-family: "Roboto Mono", monospace; background: #202020; color: #d5c4a1; @@ -62,3 +74,6 @@ button { font-size: 1em; height: 3em; } +button:focus { + background: #333; +} diff --git a/static/css/trigger_edit.css b/static/css/trigger_edit.css index 2df4fcd..41e14b4 100644 --- a/static/css/trigger_edit.css +++ b/static/css/trigger_edit.css @@ -20,6 +20,7 @@ body { body { background: #282828; font-family: "Roboto", sans-serif; + font-weight: 300; color: #d5c4a1; font-size: 11pt; } @@ -35,6 +36,16 @@ h1 { h2 { font-size: 1.25em; } +a { + color: #3f9da1; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +b { + font-weight: 500; +} .roboto-light { font-family: "Roboto", sans-serif; font-weight: 300; @@ -46,7 +57,8 @@ h2 { font-style: normal; } input[type="text"], -textarea { +textarea, +select { font-family: "Roboto Mono", monospace; background: #202020; color: #d5c4a1; @@ -62,6 +74,9 @@ button { font-size: 1em; height: 3em; } +button:focus { + background: #333; +} .widgets { display: grid; grid-template-columns: min-content 1fr; diff --git a/static/images/datapoints.svg b/static/images/datapoints.svg new file mode 100644 index 0000000..652e18f --- /dev/null +++ b/static/images/datapoints.svg @@ -0,0 +1,51 @@ + + + + + + + + file-chart + chart-timeline-variant + + + diff --git a/static/images/datapoints_selected.svg b/static/images/datapoints_selected.svg new file mode 100644 index 0000000..49b417c --- /dev/null +++ b/static/images/datapoints_selected.svg @@ -0,0 +1,49 @@ + + + + + + + + file-chart + + + diff --git a/static/images/triggers.svg b/static/images/triggers.svg index bbf749b..e4f9f3d 100644 --- a/static/images/triggers.svg +++ b/static/images/triggers.svg @@ -2,12 +2,12 @@ script-text script-text-outline + calculator-variant-outline + d="m 94.573842,148.43125 h -6.585184 c -0.517408,0 -0.940741,0.42334 -0.940741,0.94075 v 6.58517 c 0,0.51742 0.423333,0.94075 0.940741,0.94075 h 6.585184 c 0.517409,0 0.940739,-0.42333 0.940739,-0.94075 V 149.372 c 0,-0.51741 -0.42333,-0.94075 -0.940739,-0.94075 m 0,7.52592 H 87.988658 V 149.372 h 6.585184 v 6.58517 m -6.020739,-5.31518 h 2.35185 v 0.70556 h -2.35185 v -0.70556 m 3.198517,3.81001 h 2.351853 v 0.70554 H 91.75162 V 154.452 m 0,-1.22297 h 2.351853 v 0.70556 H 91.75162 v -0.70556 m -2.351851,2.25778 h 0.705556 v -0.94075 h 0.94074 v -0.70555 h -0.94074 v -0.94074 h -0.705556 v 0.94074 h -0.940741 v 0.70555 h 0.940741 v 0.94075 m 2.869259,-3.33963 0.658519,-0.65853 0.658518,0.65853 0.517408,-0.47037 -0.65852,-0.65852 0.65852,-0.65852 -0.517408,-0.51741 -0.658518,0.65853 -0.658519,-0.65853 -0.517408,0.51741 0.658519,0.65852 -0.658519,0.65852 z" + id="path1-1" + style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#777777;fill-opacity:1;stroke-width:0.423333;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1" /> diff --git a/static/images/triggers_selected.svg b/static/images/triggers_selected.svg index bd59427..0af323f 100644 --- a/static/images/triggers_selected.svg +++ b/static/images/triggers_selected.svg @@ -2,12 +2,12 @@ + transform="translate(-89.918898,-132.29942)"> script-text + calculator-variant + style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#fb4934;fill-opacity:1;stroke-width:1.22872;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;-inkscape-stroke:none;stop-color:#000000;stop-opacity:1" /> diff --git a/static/js/datapoint_edit.mjs b/static/js/datapoint_edit.mjs new file mode 100644 index 0000000..611011c --- /dev/null +++ b/static/js/datapoint_edit.mjs @@ -0,0 +1,19 @@ +export class UI { + constructor() { + document.addEventListener('keydown', evt=>this.keyHandler(evt)) + document.querySelector('input[name="name"]').focus() + } + keyHandler(evt) { + if (!(evt.altKey && evt.shiftKey)) + return + + evt.preventDefault() + evt.stopPropagation() + + switch (evt.key) { + case 'S': + document.getElementById('form-trigger').submit() + break + } + } +} diff --git a/static/js/trigger_edit.mjs b/static/js/trigger_edit.mjs index 7446699..3d18d82 100644 --- a/static/js/trigger_edit.mjs +++ b/static/js/trigger_edit.mjs @@ -12,10 +12,20 @@ export class UI { this.trigger.run() } keyHandler(evt) { - if (evt.altKey && evt.shiftKey && evt.key == 'R') { - evt.preventDefault() - evt.stopPropagation() - this.run() + if (!(evt.altKey && evt.shiftKey)) + return + + evt.preventDefault() + evt.stopPropagation() + + switch (evt.key) { + case 'T': + this.run() + break + + case 'S': + document.getElementById('form-trigger').submit() + break } } } diff --git a/static/less/datapoints.less b/static/less/datapoints.less new file mode 100644 index 0000000..afa0b13 --- /dev/null +++ b/static/less/datapoints.less @@ -0,0 +1,44 @@ +@import "theme.less"; + +#datapoints { + display: grid; + grid-template-columns: repeat(4, min-content); + grid-gap: 8px 16px; + margin-top: 16px; + + .header { + font-weight: @bold; + } + + div { + white-space: nowrap; + } +} + +.widgets { + display: grid; + grid-template-columns: min-content 1fr; + gap: 8px 16px; + + .label { + margin-top: 4px; + } + + input[type="text"], textarea { + width: 100%; + } + + .datapoints { + font: "Roboto Mono", monospace; + display: grid; + grid-template-columns: min-content 1fr; + gap: 6px 8px; + margin-bottom: 8px; + } + + .action { + display: grid; + grid-template-columns: min-content min-content; + grid-gap: 8px; + } +} diff --git a/static/less/main.less b/static/less/main.less index 0a3b18f..9c0c6f4 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -37,6 +37,7 @@ margin-bottom: 32px; div { + font-weight: 500; font-size: 1.5em; color: @color1; } @@ -61,7 +62,7 @@ &>.name { background: @color1; color: #fff; - font-weight: bold; + font-weight: 500; padding: 4px 16px; border-top-left-radius: 4px; border-top-right-radius: 4px; @@ -71,7 +72,7 @@ margin: 8px 16px; &>.name { - font-weight: bold; + font-weight: 500; } .triggers { @@ -93,7 +94,7 @@ } .label { - color: @text2; + color: @color4; } } } diff --git a/static/less/theme.less b/static/less/theme.less index fa5699a..910fda5 100644 --- a/static/less/theme.less +++ b/static/less/theme.less @@ -7,6 +7,12 @@ @error: #fb4934; @color1: #fb4934; +@color2: #fabd2f; +@color3: #b8bb26; +@color4: #3f9da1; +@color5: #fe8019; + +@bold: 500; html { box-sizing: border-box; @@ -33,6 +39,7 @@ body { body { background: @bg1; font-family: "Roboto", sans-serif; + font-weight: 300; color: @text1; font-size: 11pt; } @@ -51,6 +58,19 @@ h2 { font-size: 1.25em; } +a { + color: @color4; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +b { + font-weight: @bold; +} + .roboto-light { font-family: "Roboto", sans-serif; font-weight: 300; @@ -63,7 +83,7 @@ h2 { font-style: normal; } -input[type="text"], textarea { +input[type="text"], textarea, select { font-family: "Roboto Mono", monospace; background: @bg2; color: @text1; @@ -79,4 +99,8 @@ button { border: 1px solid lighten(@bg2, 10%); font-size: 1em; height: 3em; + + &:focus { + background: @bg3; + } } diff --git a/views/components/menu.gotmpl b/views/components/menu.gotmpl index 14722eb..68144e1 100644 --- a/views/components/menu.gotmpl +++ b/views/components/menu.gotmpl @@ -2,6 +2,7 @@ diff --git a/views/pages/datapoint_edit.gotmpl b/views/pages/datapoint_edit.gotmpl new file mode 100644 index 0000000..439b4b2 --- /dev/null +++ b/views/pages/datapoint_edit.gotmpl @@ -0,0 +1,35 @@ +{{ define "page" }} + + + + + {{ block "page_label" . }}{{end}} + +
+
+
Name
+
+ +
Datatype
+
+ +
+ +
+
+ {{ if eq .Data.Datapoint.ID 0 }} + + {{ else }} + + {{ end }} +
+ + +{{ end }} diff --git a/views/pages/datapoints.gotmpl b/views/pages/datapoints.gotmpl new file mode 100644 index 0000000..bde7414 --- /dev/null +++ b/views/pages/datapoints.gotmpl @@ -0,0 +1,26 @@ +{{ define "page" }} + + + {{ block "page_label" . }}{{end}} + + Create + +
+
Name
+
Datatype
+
Last value
+
Value
+ + {{ range .Data.Datapoints }} + +
{{ .Datatype }}
+
{{ format_time .LastValue }}
+ {{ if eq .Datatype "DATETIME" }} +
{{ if .LastDatapointValue.ValueDateTime.Valid }}{{ format_time .LastDatapointValue.Value }}{{ end }}
+ {{ else }} +
{{ .LastDatapointValue.Value }}
+ {{ end }} + {{ end }} +
+ +{{ end }} diff --git a/views/pages/trigger_edit.gotmpl b/views/pages/trigger_edit.gotmpl index 1624cd3..cae3fd3 100644 --- a/views/pages/trigger_edit.gotmpl +++ b/views/pages/trigger_edit.gotmpl @@ -13,7 +13,7 @@ {{ block "page_label" . }}{{end}} -
+
Name
@@ -21,7 +21,7 @@
Datapoints
{{ range .Data.Datapoints }} -
{{ .Name }}
+
{{ .Name }}
{{ .LastDatapointValue.Value }}
{{ end }}
@@ -31,7 +31,7 @@
- +