View datapoint values
This commit is contained in:
parent
5f6a48e7e0
commit
d72694a8b4
35
datapoint.go
35
datapoint.go
@ -2,7 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
// External
|
// External
|
||||||
we "git.gibonuddevalla.se/go/wrappederror"
|
werr "git.gibonuddevalla.se/go/wrappederror"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
@ -94,7 +94,7 @@ func DatapointAdd[T any](name string, value T) (err error) { // {{{
|
|||||||
|
|
||||||
err = row.Scan(&dpID, &dpType)
|
err = row.Scan(&dpID, &dpType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(struct {
|
err = werr.Wrap(err).WithData(struct {
|
||||||
Name string
|
Name string
|
||||||
Value any
|
Value any
|
||||||
}{name, value})
|
}{name, value})
|
||||||
@ -110,7 +110,7 @@ func DatapointAdd[T any](name string, value T) (err error) { // {{{
|
|||||||
_, err = service.Db.Conn.Exec(`INSERT INTO datapoint_value(datapoint_id, value_datetime) VALUES($1, $2)`, dpID, value)
|
_, err = service.Db.Conn.Exec(`INSERT INTO datapoint_value(datapoint_id, value_datetime) VALUES($1, $2)`, dpID, value)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(struct {
|
err = werr.Wrap(err).WithData(struct {
|
||||||
ID int
|
ID int
|
||||||
value any
|
value any
|
||||||
}{dpID, value})
|
}{dpID, value})
|
||||||
@ -147,7 +147,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{
|
|||||||
dp.name ASC
|
dp.name ASC
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err)
|
err = werr.Wrap(err)
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{
|
|||||||
res := DbRes{}
|
res := DbRes{}
|
||||||
err = rows.StructScan(&res)
|
err = rows.StructScan(&res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err)
|
err = werr.Wrap(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,7 +218,7 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(name)
|
err = werr.Wrap(err).WithData(name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +238,7 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(dp.ID)
|
err = werr.Wrap(err).WithData(dp.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +247,26 @@ func DatapointRetrieve(id int, name string) (dp Datapoint, err error) { // {{{
|
|||||||
func DatapointDelete(id int) (err error) {// {{{
|
func DatapointDelete(id int) (err error) {// {{{
|
||||||
_, err = service.Db.Conn.Exec(`DELETE FROM datapoint WHERE id=$1`, id)
|
_, err = service.Db.Conn.Exec(`DELETE FROM datapoint WHERE id=$1`, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(id)
|
err = werr.Wrap(err).WithData(id)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}// }}}
|
||||||
|
func DatapointValues(id int) (values []DatapointValue, err error) {// {{{
|
||||||
|
rows, err := service.Db.Conn.Queryx(`SELECT * FROM datapoint_value WHERE datapoint_id=$1 ORDER BY ts DESC LIMIT 500`, id)
|
||||||
|
if err != nil {
|
||||||
|
err = werr.Wrap(err).WithData(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
dpv := DatapointValue{}
|
||||||
|
err = rows.StructScan(&dpv)
|
||||||
|
if err != nil {
|
||||||
|
err = werr.Wrap(err).WithData(id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
values = append(values, dpv)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}// }}}
|
}// }}}
|
||||||
|
98
main.go
98
main.go
@ -122,6 +122,7 @@ func main() { // {{{
|
|||||||
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("/datapoint/delete/{id}", false, false, pageDatapointDelete)
|
service.Register("/datapoint/delete/{id}", false, false, pageDatapointDelete)
|
||||||
|
service.Register("/datapoint/values/{id}", false, false, pageDatapointValues)
|
||||||
|
|
||||||
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)
|
||||||
@ -194,6 +195,8 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { /
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("entry", "error", err)
|
logger.Error("entry", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Multiple triggers can use the same datapoint.
|
||||||
for _, trigger := range triggers {
|
for _, trigger := range triggers {
|
||||||
var out any
|
var out any
|
||||||
out, err = trigger.Run()
|
out, err = trigger.Run()
|
||||||
@ -204,23 +207,77 @@ func entryDatapoint(w http.ResponseWriter, r *http.Request, sess *session.T) { /
|
|||||||
}
|
}
|
||||||
logger.Debug("entry", "datapoint", dpoint, "value", value, "trigger", trigger, "result", out)
|
logger.Debug("entry", "datapoint", dpoint, "value", value, "trigger", trigger, "result", out)
|
||||||
|
|
||||||
|
var problemID int
|
||||||
switch v := out.(type) {
|
switch v := out.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
// Trigger returning true - a problem occurred
|
// Trigger returning true - a problem occurred
|
||||||
if v {
|
if v {
|
||||||
err = ProblemStart(trigger)
|
problemID, err = ProblemStart(trigger)
|
||||||
|
logger.Info("FOO", "problemID", problemID, "err==nil", err == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = werr.Wrap(err).Log()
|
err = werr.Wrap(err).Log()
|
||||||
logger.Error("entry", "error", err)
|
logger.Error("entry", "error", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
err = ProblemClose(trigger)
|
// A problem didn't occur.
|
||||||
|
problemID, err = ProblemClose(trigger)
|
||||||
|
logger.Info("FOO", "problemID", problemID, "err==nil", err == nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = werr.Wrap(err).Log()
|
err = werr.Wrap(err).Log()
|
||||||
logger.Error("entry", "error", err)
|
logger.Error("entry", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has a change in problem state happened?
|
||||||
|
if problemID == 0 && err == nil {
|
||||||
|
logger.Debug("notification", "trigger", trigger.ID, "state", "no change")
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
logger.Debug("notification", "trigger", trigger.ID, "state", "change")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = notificationManager.Send(problemID, []byte(trigger.Name), func(notificationService *notification.Service, err error) {
|
||||||
|
logger.Info(
|
||||||
|
"notification",
|
||||||
|
"service", (*notificationService).GetType(),
|
||||||
|
"problemID", problemID,
|
||||||
|
"prio", (*notificationService).GetPrio(),
|
||||||
|
"ok", true,
|
||||||
|
)
|
||||||
|
|
||||||
|
var errBody any
|
||||||
|
if err != nil {
|
||||||
|
errBody, _ = json.Marshal(err)
|
||||||
|
} else {
|
||||||
|
errBody = nil
|
||||||
|
}
|
||||||
|
_, err = service.Db.Conn.Exec(
|
||||||
|
`
|
||||||
|
INSERT INTO notification_send(notification_id, problem_id, uuid, ok, error)
|
||||||
|
SELECT
|
||||||
|
id, $3, '', $4, $5
|
||||||
|
FROM notification
|
||||||
|
WHERE
|
||||||
|
service=$1 AND
|
||||||
|
prio=$2
|
||||||
|
`,
|
||||||
|
(*notificationService).GetType(),
|
||||||
|
(*notificationService).GetPrio(),
|
||||||
|
problemID,
|
||||||
|
err == nil,
|
||||||
|
errBody,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
err = werr.Wrap(err).Log()
|
||||||
|
logger.Error("entry", "error", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = werr.Wrap(err).Log()
|
||||||
|
logger.Error("notification", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
err := fmt.Errorf(`Expression for trigger %s not returning bool (%T)`, trigger.Name, v)
|
err := fmt.Errorf(`Expression for trigger %s not returning bool (%T)`, trigger.Name, v)
|
||||||
logger.Info("entry", "error", err)
|
logger.Info("entry", "error", err)
|
||||||
@ -519,6 +576,43 @@ func pageDatapointDelete(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 pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
||||||
|
idStr := r.PathValue("id")
|
||||||
|
id, err := strconv.Atoi(idStr)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, werr.Wrap(err).Log())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var datapoint Datapoint
|
||||||
|
datapoint, err = DatapointRetrieve(id, "")
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, werr.Wrap(err).Log())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var values []DatapointValue
|
||||||
|
values, err = DatapointValues(id)
|
||||||
|
if err != nil {
|
||||||
|
httpError(w, werr.Wrap(err).Log())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page := Page{
|
||||||
|
LAYOUT: "main",
|
||||||
|
PAGE: "datapoint_values",
|
||||||
|
MENU: "datapoints",
|
||||||
|
Icon: "datapoints",
|
||||||
|
Label: "Values for "+datapoint.Name,
|
||||||
|
}
|
||||||
|
|
||||||
|
page.Data = map[string]any{
|
||||||
|
"Datapoint": datapoint,
|
||||||
|
"Values": values,
|
||||||
|
}
|
||||||
|
page.Render(w)
|
||||||
|
return
|
||||||
|
} // }}}
|
||||||
|
|
||||||
func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||||
areas, err := TriggersRetrieve()
|
areas, err := TriggersRetrieve()
|
||||||
|
@ -44,7 +44,7 @@ func (ntfy *NTFY) GetPrio() int {
|
|||||||
return ntfy.Prio
|
return ntfy.Prio
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ntfy NTFY) Send(uuid string, msg []byte) (err error) {
|
func (ntfy NTFY) Send(problemID int, msg []byte) (err error) {
|
||||||
var req *http.Request
|
var req *http.Request
|
||||||
var res *http.Response
|
var res *http.Response
|
||||||
req, err = http.NewRequest("POST", ntfy.URL, bytes.NewReader(msg))
|
req, err = http.NewRequest("POST", ntfy.URL, bytes.NewReader(msg))
|
||||||
@ -53,9 +53,9 @@ func (ntfy NTFY) Send(uuid string, msg []byte) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ackURL := fmt.Sprintf("http, OK, %s/notification/ack?uuid=%s", ntfy.AcknowledgeURL, uuid)
|
ackURL := fmt.Sprintf("http, OK, %s/notification/ack?problemID=%d", ntfy.AcknowledgeURL, problemID)
|
||||||
req.Header.Add("X-Actions", ackURL)
|
req.Header.Add("X-Actions", ackURL)
|
||||||
req.Header.Add("X-Priority", "3") // XXX: should be 5
|
req.Header.Add("X-Priority", "4") // XXX: should be 5
|
||||||
req.Header.Add("X-Tags", "calendar")
|
req.Header.Add("X-Tags", "calendar")
|
||||||
|
|
||||||
res, err = http.DefaultClient.Do(req)
|
res, err = http.DefaultClient.Do(req)
|
||||||
|
@ -13,7 +13,7 @@ type Service interface {
|
|||||||
SetLogger(*slog.Logger)
|
SetLogger(*slog.Logger)
|
||||||
GetPrio() int
|
GetPrio() int
|
||||||
GetType() string
|
GetType() string
|
||||||
Send(string, []byte) error
|
Send(int, []byte) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
@ -40,20 +40,22 @@ func (nm *Manager) AddService(service Service) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nm *Manager) Send(uuid string, msg []byte) (err error) {
|
func (nm *Manager) Send(problemID int, msg []byte, fn func(*Service, error)) (err error) {
|
||||||
for _, service := range nm.services {
|
for i, service := range nm.services {
|
||||||
nm.logger.Info("notification", "service", service.GetType(), "prio", service.GetPrio())
|
nm.logger.Info("notification", "service", service.GetType(), "prio", service.GetPrio())
|
||||||
if err = service.Send(uuid, msg); err == nil {
|
if err = service.Send(problemID, msg); err == nil {
|
||||||
|
fn(&nm.services[i], nil)
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
data := struct {
|
data := struct {
|
||||||
UUID string
|
ProblemID int
|
||||||
Msg []byte
|
Msg []byte
|
||||||
}{
|
}{
|
||||||
uuid,
|
problemID,
|
||||||
msg,
|
msg,
|
||||||
}
|
}
|
||||||
werr.Wrap(err).WithData(data).Log()
|
werr.Wrap(err).WithData(data).Log()
|
||||||
|
fn(&nm.services[i], err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
20
notification_log.go
Normal file
20
notification_log.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
// Internal
|
||||||
|
"smon/notification"
|
||||||
|
// Standard
|
||||||
|
)
|
||||||
|
|
||||||
|
func notificationLog(notificationService *notification.Service, problemID int, err error) {
|
||||||
|
if err == nil {
|
||||||
|
logger.Info("notification", "service", (*notificationService).GetType(), "problemID", problemID, "prio", (*notificationService).GetPrio(), "ok", true)
|
||||||
|
service.Db.Conn.Query(
|
||||||
|
`
|
||||||
|
INSERT INTO notification_send()
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.Error("notification", "service", (*notificationService).GetType(), "problemID", problemID, "prio", (*notificationService).GetPrio(), "ok", false, "error", err)
|
||||||
|
}
|
||||||
|
}
|
16
problem.go
16
problem.go
@ -66,7 +66,7 @@ func ProblemsRetrieve() (problems []Problem, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProblemStart(trigger Trigger) (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
|
||||||
@ -86,7 +86,8 @@ func ProblemStart(trigger Trigger) (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 {
|
||||||
_, err = service.Db.Conn.Exec(`INSERT INTO problem(trigger_id) VALUES($1)`, trigger.ID)
|
row = service.Db.Conn.QueryRow(`INSERT INTO problem(trigger_id) VALUES($1) RETURNING id`, trigger.ID)
|
||||||
|
err = row.Scan(&problemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(trigger)
|
err = we.Wrap(err).WithData(trigger)
|
||||||
}
|
}
|
||||||
@ -94,8 +95,15 @@ func ProblemStart(trigger Trigger) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProblemClose(trigger Trigger) (err error) {
|
func ProblemClose(trigger Trigger) (problemID int, err error) {
|
||||||
_, err = service.Db.Conn.Exec(`UPDATE problem SET "end"=NOW() WHERE trigger_id=$1 AND "end" IS NULL`, 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)
|
||||||
|
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(trigger)
|
err = we.Wrap(err).WithData(trigger)
|
||||||
return
|
return
|
||||||
|
13
sql/00012.sql
Normal file
13
sql/00012.sql
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
CREATE TABLE public.notification_send (
|
||||||
|
id serial NOT NULL,
|
||||||
|
notification_id int4 NOT NULL,
|
||||||
|
"uuid" char(36) NOT NULL,
|
||||||
|
send timestamptz DEFAULT now() NOT NULL,
|
||||||
|
ok bool NOT NULL,
|
||||||
|
error jsonb NULL,
|
||||||
|
acknowledged bool DEFAULT false NOT NULL,
|
||||||
|
problem_id int8 NOT NULL,
|
||||||
|
CONSTRAINT notification_send_pk PRIMARY KEY (id),
|
||||||
|
CONSTRAINT notification_send_notification_fk FOREIGN KEY (notification_id) REFERENCES public.notification(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT notification_send_problem_fk FOREIGN KEY (problem_id) REFERENCES public.problem(id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
@ -109,6 +109,17 @@ label {
|
|||||||
#datapoints div {
|
#datapoints div {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
#datapoints .icons {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
#values {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, min-content);
|
||||||
|
gap: 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
.widgets {
|
.widgets {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
|
@ -101,7 +101,6 @@ label {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "menu content";
|
grid-template-areas: "menu content";
|
||||||
grid-template-columns: 64px 1fr;
|
grid-template-columns: 64px 1fr;
|
||||||
grid-template-rows: 100% 100%;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
#menu {
|
#menu {
|
||||||
|
67
static/images/values.svg
Normal file
67
static/images/values.svg
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="18.500002"
|
||||||
|
height="15"
|
||||||
|
viewBox="0 0 4.8947921 3.9687501"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
||||||
|
sodipodi:docname="points.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="-33"
|
||||||
|
inkscape:cy="-79"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1093"
|
||||||
|
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(-81.359375,-211.79896)">
|
||||||
|
<title
|
||||||
|
id="title1">format-list-bulleted</title>
|
||||||
|
<path
|
||||||
|
d="m 82.55,211.93125 h 3.704167 v 0.52917 H 82.55 v -0.52917 m 0,2.11667 v -0.52917 h 3.704167 v 0.52917 H 82.55 m -0.79375,-2.24896 a 0.396875,0.396875 0 0 1 0.396875,0.39687 0.396875,0.396875 0 0 1 -0.396875,0.39688 0.396875,0.396875 0 0 1 -0.396875,-0.39688 0.396875,0.396875 0 0 1 0.396875,-0.39687 m 0,1.5875 a 0.396875,0.396875 0 0 1 0.396875,0.39687 0.396875,0.396875 0 0 1 -0.396875,0.39688 0.396875,0.396875 0 0 1 -0.396875,-0.39688 0.396875,0.396875 0 0 1 0.396875,-0.39687 m 0.79375,2.24896 v -0.52917 h 3.704167 v 0.52917 H 82.55 m -0.79375,-0.66146 a 0.396875,0.396875 0 0 1 0.396875,0.39687 0.396875,0.396875 0 0 1 -0.396875,0.39688 0.396875,0.396875 0 0 1 -0.396875,-0.39688 0.396875,0.396875 0 0 1 0.396875,-0.39687 z"
|
||||||
|
id="path1"
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#fb4934;fill-opacity:1;stroke-width:0.384845;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" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
@ -13,6 +13,19 @@
|
|||||||
div {
|
div {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icons {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#values {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, min-content);
|
||||||
|
gap: 16px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.widgets {
|
.widgets {
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "menu content";
|
grid-template-areas: "menu content";
|
||||||
grid-template-columns: 64px 1fr;
|
grid-template-columns: 64px 1fr;
|
||||||
grid-template-rows:
|
|
||||||
100% 100%;
|
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
views/pages/datapoint_values.gotmpl
Normal file
13
views/pages/datapoint_values.gotmpl
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{{ define "page" }}
|
||||||
|
{{ $version := .VERSION }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/datapoints.css">
|
||||||
|
|
||||||
|
{{ block "page_label" . }}{{end}}
|
||||||
|
|
||||||
|
<div id="values">
|
||||||
|
{{ range .Data.Values }}
|
||||||
|
<div class="value">{{ format_time .Ts }}</div>
|
||||||
|
<div class="value">{{ .Value }}</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
@ -23,7 +23,10 @@
|
|||||||
{{ else }}
|
{{ else }}
|
||||||
<div class="value">{{ .LastDatapointValue.Value }}</div>
|
<div class="value">{{ .LastDatapointValue.Value }}</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
<div class="icons">
|
||||||
|
<div class="values"><a href="/datapoint/values/{{ .ID }}"><img src="/images/{{ $version }}/values.svg"></a></div>
|
||||||
<div class="delete"><a href="/datapoint/delete/{{ .ID }}" onclick="confirm(`Are you sure you want to delete '{{ .Name }}'?`)"><img src="/images/{{ $version }}/delete.svg"></a></div>
|
<div class="delete"><a href="/datapoint/delete/{{ .ID }}" onclick="confirm(`Are you sure you want to delete '{{ .Name }}'?`)"><img src="/images/{{ $version }}/delete.svg"></a></div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
Loading…
Reference in New Issue
Block a user