Implemented basic functions
This commit is contained in:
parent
89f483171a
commit
965e2daeb3
142
datapoint.go
142
datapoint.go
@ -3,9 +3,12 @@ package main
|
|||||||
import (
|
import (
|
||||||
// External
|
// External
|
||||||
we "git.gibonuddevalla.se/go/wrappederror"
|
we "git.gibonuddevalla.se/go/wrappederror"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
|
||||||
// Standard
|
// Standard
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +23,6 @@ const (
|
|||||||
type Datapoint struct {
|
type Datapoint struct {
|
||||||
ID int
|
ID int
|
||||||
Name string
|
Name string
|
||||||
SectionID int `db:"section_id"`
|
|
||||||
Datatype DatapointType
|
Datatype DatapointType
|
||||||
LastValue time.Time `db:"last_value"`
|
LastValue time.Time `db:"last_value"`
|
||||||
DatapointValueJSON []byte `db:"datapoint_value_json"`
|
DatapointValueJSON []byte `db:"datapoint_value_json"`
|
||||||
@ -36,20 +38,53 @@ type DatapointValue struct {
|
|||||||
ValueDateTime sql.NullTime `db:"value_datetime"`
|
ValueDateTime sql.NullTime `db:"value_datetime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dp DatapointValue) Value() any {
|
func (dp DatapointValue) Value() any { // {{{
|
||||||
if dp.ValueInt.Valid {
|
if dp.ValueInt.Valid {
|
||||||
return dp.ValueInt.Int64
|
return dp.ValueInt.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
if dp.ValueString.Valid {
|
if dp.ValueString.Valid {
|
||||||
return dp.ValueString.String
|
return dp.ValueString.String
|
||||||
}
|
}
|
||||||
|
|
||||||
if dp.ValueDateTime.Valid {
|
if dp.ValueDateTime.Valid {
|
||||||
return dp.ValueDateTime.Time
|
return dp.ValueDateTime.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
row := service.Db.Conn.QueryRow(`SELECT id, datatype FROM datapoint WHERE name=$1`, name)
|
||||||
|
|
||||||
var dpID int
|
var dpID int
|
||||||
@ -81,13 +116,94 @@ func DatapointAdd[T any](name string, value T) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
} // }}}
|
||||||
|
|
||||||
func DatapointRetrieve(name string) (dp Datapoint, err error) {
|
func DatapointsRetrieve() (dps []Datapoint, err error) { // {{{
|
||||||
row := service.Db.Conn.QueryRowx(
|
dps = []Datapoint{}
|
||||||
`SELECT * FROM datapoint dp WHERE dp.name = $1`,
|
var rows *sqlx.Rows
|
||||||
name,
|
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)
|
err = row.StructScan(&dp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(name)
|
err = we.Wrap(err).WithData(name)
|
||||||
@ -104,9 +220,15 @@ func DatapointRetrieve(name string) (dp Datapoint, err error) {
|
|||||||
dp.ID,
|
dp.ID,
|
||||||
)
|
)
|
||||||
err = row.StructScan(&dp.LastDatapointValue)
|
err = row.StructScan(&dp.LastDatapointValue)
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
err = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).WithData(dp.ID)
|
err = we.Wrap(err).WithData(dp.ID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
} // }}}
|
||||||
|
109
main.go
109
main.go
@ -20,6 +20,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "v1"
|
const VERSION = "v1"
|
||||||
@ -88,8 +89,23 @@ func main() { // {{{
|
|||||||
service.SetDatabase(sqlProvider)
|
service.SetDatabase(sqlProvider)
|
||||||
service.SetStaticFS(staticFS, "static")
|
service.SetStaticFS(staticFS, "static")
|
||||||
service.SetStaticDirectory("static", flagDev)
|
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("/", false, false, staticHandler)
|
||||||
service.Register("/problems", false, false, pageProblems)
|
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("/triggers", false, false, pageTriggers)
|
||||||
service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit)
|
service.Register("/trigger/edit/{id}", false, false, pageTriggerEdit)
|
||||||
service.Register("/trigger/update/{id}", false, false, pageTriggerUpdate)
|
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
|
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 := []string{layoutFilename, pageFilename}
|
||||||
filenames = append(filenames, componentFilenames...)
|
filenames = append(filenames, componentFilenames...)
|
||||||
logger.Info("template", "op", "parse", "layout", layout, "page", page, "filenames", filenames)
|
logger.Info("template", "op", "parse", "layout", layout, "page", page, "filenames", filenames)
|
||||||
if flagDev {
|
if flagDev {
|
||||||
parsedTemplates[page], err = template.ParseFS(os.DirFS("."), filenames...)
|
tmpl, err = template.New("main.gotmpl").Funcs(funcMap).ParseFS(os.DirFS("."), filenames...)
|
||||||
} else {
|
} else {
|
||||||
parsedTemplates[page], err = template.ParseFS(viewFS, filenames...)
|
tmpl, err = template.New("main.gotmpl").ParseFS(viewFS, filenames...)
|
||||||
}
|
}
|
||||||
tmpl = parsedTemplates[page]
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = we.Wrap(err).Log()
|
err = we.Wrap(err).Log()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parsedTemplates[page] = tmpl
|
||||||
return
|
return
|
||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
@ -237,6 +258,80 @@ func pageProblems(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
|||||||
page.Render(w)
|
page.Render(w)
|
||||||
return
|
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) { // {{{
|
func pageTriggers(w http.ResponseWriter, _ *http.Request, _ *session.T) { // {{{
|
||||||
areas, err := TriggersRetrieve()
|
areas, err := TriggersRetrieve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -275,7 +370,7 @@ func pageTriggerEdit(w http.ResponseWriter, r *http.Request, _ *session.T) { //
|
|||||||
|
|
||||||
datapoints := make(map[string]Datapoint)
|
datapoints := make(map[string]Datapoint)
|
||||||
for _, dpname := range trigger.Datapoints {
|
for _, dpname := range trigger.Datapoints {
|
||||||
dp, err := DatapointRetrieve(dpname)
|
dp, err := DatapointRetrieve(0, dpname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
@ -320,7 +415,7 @@ func pageTriggerUpdate(w http.ResponseWriter, r *http.Request, _ *session.T) { /
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Location", fmt.Sprintf("/trigger/edit/%d", id))
|
w.Header().Add("Location", "/triggers")
|
||||||
w.WriteHeader(302)
|
w.WriteHeader(302)
|
||||||
} // }}}
|
} // }}}
|
||||||
func pageTriggerRun(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
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)
|
datapoints := make(map[string]Datapoint)
|
||||||
for _, dpname := range trigger.Datapoints {
|
for _, dpname := range trigger.Datapoints {
|
||||||
dp, err := DatapointRetrieve(dpname)
|
dp, err := DatapointRetrieve(0, dpname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpError(w, we.Wrap(err).Log())
|
httpError(w, we.Wrap(err).Log())
|
||||||
return
|
return
|
||||||
|
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())
|
||||||
|
9
sql/00006.sql
Normal file
9
sql/00006.sql
Normal file
@ -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
|
||||||
|
);
|
1
sql/00007.sql
Normal file
1
sql/00007.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE datapoint DROP COLUMN section_id;
|
115
static/css/datapoints.css
Normal file
115
static/css/datapoints.css
Normal file
@ -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;
|
||||||
|
}
|
@ -20,6 +20,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
@ -35,6 +36,16 @@ h1 {
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: #3f9da1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
.roboto-light {
|
.roboto-light {
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@ -46,7 +57,8 @@ h2 {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
textarea {
|
textarea,
|
||||||
|
select {
|
||||||
font-family: "Roboto Mono", monospace;
|
font-family: "Roboto Mono", monospace;
|
||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
@ -62,6 +74,9 @@ button {
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
button:focus {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
#layout {
|
#layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-areas: "menu content";
|
grid-template-areas: "menu content";
|
||||||
@ -94,6 +109,7 @@ button {
|
|||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
}
|
}
|
||||||
#page .page-label div {
|
#page .page-label div {
|
||||||
|
font-weight: 500;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
color: #fb4934;
|
color: #fb4934;
|
||||||
}
|
}
|
||||||
@ -113,7 +129,7 @@ button {
|
|||||||
#areas .area > .name {
|
#areas .area > .name {
|
||||||
background: #fb4934;
|
background: #fb4934;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
@ -122,7 +138,7 @@ button {
|
|||||||
margin: 8px 16px;
|
margin: 8px 16px;
|
||||||
}
|
}
|
||||||
#areas .area .section > .name {
|
#areas .area .section > .name {
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
#areas .area .section .triggers a {
|
#areas .area .section .triggers a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
@ -139,5 +155,5 @@ button {
|
|||||||
height: 16px;
|
height: 16px;
|
||||||
}
|
}
|
||||||
#areas .area .section .triggers .trigger .label {
|
#areas .area .section .triggers .trigger .label {
|
||||||
color: #f7edd7;
|
color: #3f9da1;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
@ -35,6 +36,16 @@ h1 {
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: #3f9da1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
.roboto-light {
|
.roboto-light {
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@ -46,7 +57,8 @@ h2 {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
textarea {
|
textarea,
|
||||||
|
select {
|
||||||
font-family: "Roboto Mono", monospace;
|
font-family: "Roboto Mono", monospace;
|
||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
@ -62,3 +74,6 @@ button {
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
button:focus {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
@ -20,6 +20,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
background: #282828;
|
background: #282828;
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
@ -35,6 +36,16 @@ h1 {
|
|||||||
h2 {
|
h2 {
|
||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
color: #3f9da1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
b {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
.roboto-light {
|
.roboto-light {
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@ -46,7 +57,8 @@ h2 {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
textarea {
|
textarea,
|
||||||
|
select {
|
||||||
font-family: "Roboto Mono", monospace;
|
font-family: "Roboto Mono", monospace;
|
||||||
background: #202020;
|
background: #202020;
|
||||||
color: #d5c4a1;
|
color: #d5c4a1;
|
||||||
@ -62,6 +74,9 @@ button {
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
}
|
}
|
||||||
|
button:focus {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
.widgets {
|
.widgets {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: min-content 1fr;
|
grid-template-columns: min-content 1fr;
|
||||||
|
51
static/images/datapoints.svg
Normal file
51
static/images/datapoints.svg
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="32.000126"
|
||||||
|
height="17.454464"
|
||||||
|
viewBox="0 0 8.4667 4.6181601"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
||||||
|
sodipodi:docname="datapoints.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">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:zoom="8.516629"
|
||||||
|
inkscape:cx="7.9843798"
|
||||||
|
inkscape:cy="-12.622365"
|
||||||
|
inkscape:window-width="1916"
|
||||||
|
inkscape:window-height="1041"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-102.9229,-148.76801)">
|
||||||
|
<title
|
||||||
|
id="title1">file-chart</title>
|
||||||
|
<title
|
||||||
|
id="title1-6">chart-timeline-variant</title>
|
||||||
|
<path
|
||||||
|
d="m 103.69267,151.8468 0.19242,0.0269 1.75874,-1.75874 c -0.0692,-0.25015 -0.004,-0.53493 0.20012,-0.73505 0.30018,-0.30402 0.78508,-0.30402 1.08527,0 0.20396,0.20012 0.26937,0.4849 0.20011,0.73505 l 0.98905,0.98905 0.19242,-0.0269 c 0.0692,0 0.13469,0 0.19242,0.0269 l 1.37389,-1.37389 c -0.0269,-0.0577 -0.0269,-0.1232 -0.0269,-0.19242 a 0.76968815,0.76968815 0 0 1 0.76969,-0.76969 0.76968815,0.76968815 0 0 1 0.7697,0.76969 0.76968815,0.76968815 0 0 1 -0.7697,0.76968 c -0.0692,0 -0.13469,0 -0.19242,-0.0269 l -1.37389,1.3739 c 0.0269,0.0577 0.0269,0.1232 0.0269,0.19242 a 0.76968815,0.76968815 0 0 1 -0.76969,0.76968 0.76968815,0.76968815 0 0 1 -0.76969,-0.76968 l 0.0269,-0.19242 -0.98904,-0.98905 c -0.1232,0.0269 -0.2617,0.0269 -0.38485,0 l -1.75873,1.75873 0.0269,0.19242 a 0.76968815,0.76968815 0 0 1 -0.7697,0.76969 0.76968815,0.76968815 0 0 1 -0.76969,-0.76969 0.76968815,0.76968815 0 0 1 0.76969,-0.76968 z"
|
||||||
|
id="path1-2"
|
||||||
|
style="stroke-width:0.384844;fill:#777777;fill-opacity:1" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
49
static/images/datapoints_selected.svg
Normal file
49
static/images/datapoints_selected.svg
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="8.4666996mm"
|
||||||
|
height="4.6181598mm"
|
||||||
|
viewBox="0 0 8.4666996 4.6181597"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
||||||
|
sodipodi:docname="datapoints_selected.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">
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="3.1691345"
|
||||||
|
inkscape:cx="-0.9466307"
|
||||||
|
inkscape:cy="-2.3665768"
|
||||||
|
inkscape:window-width="1916"
|
||||||
|
inkscape:window-height="1041"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="18"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1" />
|
||||||
|
<defs
|
||||||
|
id="defs1" />
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(-100.80625,-145.78542)">
|
||||||
|
<title
|
||||||
|
id="title1">file-chart</title>
|
||||||
|
<path
|
||||||
|
d="m 101.57602,148.86421 0.19242,0.0269 1.75874,-1.75874 c -0.0692,-0.25015 -0.004,-0.53493 0.20012,-0.73505 0.30018,-0.30402 0.78508,-0.30402 1.08527,0 0.20396,0.20012 0.26937,0.4849 0.20011,0.73505 l 0.98905,0.98905 0.19242,-0.0269 c 0.0692,0 0.13469,0 0.19242,0.0269 l 1.37389,-1.37389 c -0.0269,-0.0577 -0.0269,-0.1232 -0.0269,-0.19242 a 0.76968815,0.76968815 0 0 1 0.76969,-0.76969 0.76968815,0.76968815 0 0 1 0.7697,0.76969 0.76968815,0.76968815 0 0 1 -0.7697,0.76968 c -0.0692,0 -0.13469,0 -0.19242,-0.0269 l -1.37389,1.3739 c 0.0269,0.0577 0.0269,0.1232 0.0269,0.19242 a 0.76968815,0.76968815 0 0 1 -0.76969,0.76968 0.76968815,0.76968815 0 0 1 -0.76969,-0.76968 l 0.0269,-0.19242 -0.98904,-0.98905 c -0.1232,0.0269 -0.2617,0.0269 -0.38485,0 l -1.75873,1.75873 0.0269,0.19242 a 0.76968815,0.76968815 0 0 1 -0.7697,0.76969 0.76968815,0.76968815 0 0 1 -0.76969,-0.76969 0.76968815,0.76968815 0 0 1 0.76969,-0.76968 z"
|
||||||
|
id="path1-2"
|
||||||
|
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#fb4934;fill-opacity:1;stroke-width:0.529166;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.6 KiB |
@ -2,12 +2,12 @@
|
|||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
width="32"
|
width="31.99999"
|
||||||
height="32.000011"
|
height="32.000011"
|
||||||
viewBox="0 0 8.4666665 8.4666699"
|
viewBox="0 0 8.466664 8.4666699"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg8"
|
id="svg8"
|
||||||
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
||||||
sodipodi:docname="triggers.svg"
|
sodipodi:docname="triggers.svg"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
@ -26,16 +26,16 @@
|
|||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="1"
|
inkscape:zoom="1"
|
||||||
inkscape:cx="7.5"
|
inkscape:cx="7"
|
||||||
inkscape:cy="4"
|
inkscape:cy="3.5"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
units="px"
|
units="px"
|
||||||
inkscape:window-width="2190"
|
inkscape:window-width="1916"
|
||||||
inkscape:window-height="1404"
|
inkscape:window-height="1041"
|
||||||
inkscape:window-x="1463"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="16"
|
inkscape:window-y="18"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="0"
|
||||||
inkscape:showpageshadow="true"
|
inkscape:showpageshadow="true"
|
||||||
inkscape:pagecheckerboard="0"
|
inkscape:pagecheckerboard="0"
|
||||||
@ -61,9 +61,11 @@
|
|||||||
id="title1">script-text</title>
|
id="title1">script-text</title>
|
||||||
<title
|
<title
|
||||||
id="title1-6">script-text-outline</title>
|
id="title1-6">script-text-outline</title>
|
||||||
|
<title
|
||||||
|
id="title1-9">calculator-variant-outline</title>
|
||||||
<path
|
<path
|
||||||
d="m 92.551251,156.05126 a 0.42333338,0.42333371 0 0 0 0.423332,-0.42334 v -6.35001 h -3.386666 a 0.42333338,0.42333371 0 0 0 -0.423334,0.42334 v 4.65666 h -0.846666 v -4.65666 a 1.2700002,1.2700012 0 0 1 1.27,-1.27 h 4.656667 a 1.2700002,1.2700012 0 0 1 1.27,1.27 v 0.42333 h -0.846666 v -0.42333 a 0.42333338,0.42333371 0 0 0 -0.423334,-0.42334 0.42333338,0.42333371 0 0 0 -0.423333,0.42334 v 1.69333 4.23334 a 1.2700002,1.2700012 0 0 1 -1.27,1.27 h -4.233334 a 1.2700002,1.2700012 0 0 1 -1.27,-1.27 v -0.42333 h 4.656666 a 0.84666678,0.84666745 0 0 0 0.846668,0.84667 m -2.540001,-5.92668 h 2.116668 v 0.84667 H 90.01125 v -0.84667 m 0,1.69333 h 2.116668 v 0.84667 H 90.01125 v -0.84667 m 0,1.69334 h 2.116668 v 0.84666 H 90.01125 Z"
|
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"
|
id="path1-1"
|
||||||
style="stroke-width:0.423333;fill:#777777;fill-opacity: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" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 3.1 KiB |
@ -2,12 +2,12 @@
|
|||||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
<svg
|
<svg
|
||||||
width="32"
|
width="31.99999"
|
||||||
height="32.000011"
|
height="32.000011"
|
||||||
viewBox="0 0 8.4666665 8.4666699"
|
viewBox="0 0 8.466664 8.4666699"
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="svg8"
|
id="svg8"
|
||||||
inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)"
|
inkscape:version="1.3.2 (091e20e, 2023-11-25)"
|
||||||
sodipodi:docname="triggers_selected.svg"
|
sodipodi:docname="triggers_selected.svg"
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
@ -26,16 +26,16 @@
|
|||||||
inkscape:pageopacity="0.0"
|
inkscape:pageopacity="0.0"
|
||||||
inkscape:pageshadow="2"
|
inkscape:pageshadow="2"
|
||||||
inkscape:zoom="1"
|
inkscape:zoom="1"
|
||||||
inkscape:cx="7.5"
|
inkscape:cx="-4"
|
||||||
inkscape:cy="4"
|
inkscape:cy="64.5"
|
||||||
inkscape:document-units="px"
|
inkscape:document-units="px"
|
||||||
inkscape:current-layer="layer1"
|
inkscape:current-layer="layer1"
|
||||||
showgrid="false"
|
showgrid="false"
|
||||||
units="px"
|
units="px"
|
||||||
inkscape:window-width="2190"
|
inkscape:window-width="1916"
|
||||||
inkscape:window-height="1404"
|
inkscape:window-height="1041"
|
||||||
inkscape:window-x="1463"
|
inkscape:window-x="0"
|
||||||
inkscape:window-y="16"
|
inkscape:window-y="18"
|
||||||
inkscape:window-maximized="0"
|
inkscape:window-maximized="0"
|
||||||
inkscape:showpageshadow="true"
|
inkscape:showpageshadow="true"
|
||||||
inkscape:pagecheckerboard="0"
|
inkscape:pagecheckerboard="0"
|
||||||
@ -56,12 +56,14 @@
|
|||||||
inkscape:label="Layer 1"
|
inkscape:label="Layer 1"
|
||||||
inkscape:groupmode="layer"
|
inkscape:groupmode="layer"
|
||||||
id="layer1"
|
id="layer1"
|
||||||
transform="translate(-87.047917,-148.43125)">
|
transform="translate(-89.918898,-132.29942)">
|
||||||
<title
|
<title
|
||||||
id="title1">script-text</title>
|
id="title1">script-text</title>
|
||||||
|
<title
|
||||||
|
id="title1-9">calculator-variant</title>
|
||||||
<path
|
<path
|
||||||
d="m 93.736584,156.05125 c -0.169334,0.508 -0.635,0.84667 -1.185334,0.84667 h -4.233333 c -0.719667,0 -1.27,-0.55033 -1.27,-1.27 v -0.42334 h 1.27 3.894667 c 0.169333,0.508 0.635,0.84667 1.185333,0.84667 h 0.338667 m 0.508,-7.62 c 0.719666,0 1.27,0.55034 1.27,1.27 v 0.42333 h -0.846667 v -0.42333 c 0,-0.254 -0.169333,-0.42333 -0.423333,-0.42333 -0.254,0 -0.423334,0.16933 -0.423334,0.42333 v 5.50333 h -0.423333 c -0.254,0 -0.423333,-0.16933 -0.423333,-0.42333 v -0.42333 h -4.656667 v -4.65667 c 0,-0.71966 0.550333,-1.27 1.27,-1.27 h 4.656667 m -4.656667,1.69333 v 0.84667 h 2.963333 v -0.84667 h -2.963333 m 0,1.69334 v 0.84666 h 2.54 v -0.84666 z"
|
d="m 97.444823,132.29942 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.517408,0 0.940742,-0.42333 0.940742,-0.94075 v -6.58517 c 0,-0.51741 -0.423334,-0.94075 -0.940742,-0.94075 m -2.822222,1.92852 0.517408,-0.51741 0.658519,0.65853 0.658518,-0.65853 0.517408,0.51741 -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 m -3.198517,0.28222 h 2.35185 v 0.70556 h -2.35185 v -0.70556 m 2.492962,3.90407 h -0.94074 v 0.94075 H 92.27075 v -0.94075 h -0.940741 v -0.70555 h 0.940741 v -0.94074 h 0.705556 v 0.94074 h 0.94074 v 0.70555 m 3.057408,0.56444 h -2.351853 v -0.70554 h 2.351853 v 0.70554 m 0,-1.12889 h -2.351853 v -0.70554 h 2.351853 z"
|
||||||
id="path1"
|
id="path1"
|
||||||
style="font-variation-settings:normal;opacity:1;vector-effect:none;fill:#fb4934;fill-opacity:1;stroke-width:0.691155;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" />
|
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" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.0 KiB |
19
static/js/datapoint_edit.mjs
Normal file
19
static/js/datapoint_edit.mjs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,10 +12,20 @@ export class UI {
|
|||||||
this.trigger.run()
|
this.trigger.run()
|
||||||
}
|
}
|
||||||
keyHandler(evt) {
|
keyHandler(evt) {
|
||||||
if (evt.altKey && evt.shiftKey && evt.key == 'R') {
|
if (!(evt.altKey && evt.shiftKey))
|
||||||
|
return
|
||||||
|
|
||||||
evt.preventDefault()
|
evt.preventDefault()
|
||||||
evt.stopPropagation()
|
evt.stopPropagation()
|
||||||
|
|
||||||
|
switch (evt.key) {
|
||||||
|
case 'T':
|
||||||
this.run()
|
this.run()
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'S':
|
||||||
|
document.getElementById('form-trigger').submit()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
static/less/datapoints.less
Normal file
44
static/less/datapoints.less
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,7 @@
|
|||||||
margin-bottom: 32px;
|
margin-bottom: 32px;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
|
font-weight: 500;
|
||||||
font-size: 1.5em;
|
font-size: 1.5em;
|
||||||
color: @color1;
|
color: @color1;
|
||||||
}
|
}
|
||||||
@ -61,7 +62,7 @@
|
|||||||
&>.name {
|
&>.name {
|
||||||
background: @color1;
|
background: @color1;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
padding: 4px 16px;
|
padding: 4px 16px;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
@ -71,7 +72,7 @@
|
|||||||
margin: 8px 16px;
|
margin: 8px 16px;
|
||||||
|
|
||||||
&>.name {
|
&>.name {
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.triggers {
|
.triggers {
|
||||||
@ -93,7 +94,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
color: @text2;
|
color: @color4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,12 @@
|
|||||||
|
|
||||||
@error: #fb4934;
|
@error: #fb4934;
|
||||||
@color1: #fb4934;
|
@color1: #fb4934;
|
||||||
|
@color2: #fabd2f;
|
||||||
|
@color3: #b8bb26;
|
||||||
|
@color4: #3f9da1;
|
||||||
|
@color5: #fe8019;
|
||||||
|
|
||||||
|
@bold: 500;
|
||||||
|
|
||||||
html {
|
html {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@ -33,6 +39,7 @@ body {
|
|||||||
body {
|
body {
|
||||||
background: @bg1;
|
background: @bg1;
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
color: @text1;
|
color: @text1;
|
||||||
font-size: 11pt;
|
font-size: 11pt;
|
||||||
}
|
}
|
||||||
@ -51,6 +58,19 @@ h2 {
|
|||||||
font-size: 1.25em;
|
font-size: 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: @color4;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b {
|
||||||
|
font-weight: @bold;
|
||||||
|
}
|
||||||
|
|
||||||
.roboto-light {
|
.roboto-light {
|
||||||
font-family: "Roboto", sans-serif;
|
font-family: "Roboto", sans-serif;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
@ -63,7 +83,7 @@ h2 {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"], textarea {
|
input[type="text"], textarea, select {
|
||||||
font-family: "Roboto Mono", monospace;
|
font-family: "Roboto Mono", monospace;
|
||||||
background: @bg2;
|
background: @bg2;
|
||||||
color: @text1;
|
color: @text1;
|
||||||
@ -79,4 +99,8 @@ button {
|
|||||||
border: 1px solid lighten(@bg2, 10%);
|
border: 1px solid lighten(@bg2, 10%);
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
height: 3em;
|
height: 3em;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: @bg3;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<div id="menu">
|
<div id="menu">
|
||||||
<a href="/"><img src="/images/{{ .VERSION }}/logo{{ if eq .MENU "index" }}_selected{{ end }}.svg"></a>
|
<a href="/"><img src="/images/{{ .VERSION }}/logo{{ if eq .MENU "index" }}_selected{{ end }}.svg"></a>
|
||||||
<a href="/problems"><img src="/images/{{ .VERSION }}/problems{{ if eq .MENU "problems" }}_selected{{ end }}.svg"></a>
|
<a href="/problems"><img src="/images/{{ .VERSION }}/problems{{ if eq .MENU "problems" }}_selected{{ end }}.svg"></a>
|
||||||
|
<a href="/datapoints"><img src="/images/{{ .VERSION }}/datapoints{{ if eq .MENU "datapoints" }}_selected{{ end }}.svg"></a>
|
||||||
<a href="/triggers"><img src="/images/{{ .VERSION }}/triggers{{ if eq .MENU "triggers" }}_selected{{ end }}.svg"></a>
|
<a href="/triggers"><img src="/images/{{ .VERSION }}/triggers{{ if eq .MENU "triggers" }}_selected{{ end }}.svg"></a>
|
||||||
<a href="/configuration"><img src="/images/{{ .VERSION }}/configuration{{ if eq .MENU "configuration" }}_selected{{ end }}.svg"></a>
|
<a href="/configuration"><img src="/images/{{ .VERSION }}/configuration{{ if eq .MENU "configuration" }}_selected{{ end }}.svg"></a>
|
||||||
</div>
|
</div>
|
||||||
|
35
views/pages/datapoint_edit.gotmpl
Normal file
35
views/pages/datapoint_edit.gotmpl
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
{{ define "page" }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/datapoints.css">
|
||||||
|
|
||||||
|
<script type="module" defer>
|
||||||
|
import {UI} from "/js/{{ .VERSION }}/datapoint_edit.mjs"
|
||||||
|
window._ui = new UI()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{{ block "page_label" . }}{{end}}
|
||||||
|
|
||||||
|
<form id="form-trigger" action="/datapoint/update/{{ .Data.Datapoint.ID }}" method="post">
|
||||||
|
<div id="widgets" class="widgets">
|
||||||
|
<div class="label">Name</div>
|
||||||
|
<div><input type="text" name="name" value="{{ .Data.Datapoint.Name }}"></div>
|
||||||
|
|
||||||
|
<div class="label">Datatype</div>
|
||||||
|
<div>
|
||||||
|
<select name="datatype">
|
||||||
|
<option {{ if eq .Data.Datapoint.Datatype "INT" }}selected{{end}}>INT</option>
|
||||||
|
<option {{ if eq .Data.Datapoint.Datatype "STRING" }}selected{{end}}>STRING</option>
|
||||||
|
<option {{ if eq .Data.Datapoint.Datatype "DATETIME" }}selected{{end}}>DATETIME</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div></div>
|
||||||
|
<div class="action">
|
||||||
|
{{ if eq .Data.Datapoint.ID 0 }}
|
||||||
|
<button id="button-update">Create</button>
|
||||||
|
{{ else }}
|
||||||
|
<button id="button-update">Update</button>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{{ end }}
|
26
views/pages/datapoints.gotmpl
Normal file
26
views/pages/datapoints.gotmpl
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{{ define "page" }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/datapoints.css">
|
||||||
|
|
||||||
|
{{ block "page_label" . }}{{end}}
|
||||||
|
|
||||||
|
<a href="/datapoint/edit/0">Create</a>
|
||||||
|
|
||||||
|
<div id="datapoints">
|
||||||
|
<div class="header">Name</div>
|
||||||
|
<div class="header">Datatype</div>
|
||||||
|
<div class="header">Last value</div>
|
||||||
|
<div class="header">Value</div>
|
||||||
|
|
||||||
|
{{ range .Data.Datapoints }}
|
||||||
|
<div class="name"><a href="/datapoint/edit/{{ .ID }}">{{ .Name }}</a></div>
|
||||||
|
<div class="datatype">{{ .Datatype }}</div>
|
||||||
|
<div class="last-value">{{ format_time .LastValue }}</div>
|
||||||
|
{{ if eq .Datatype "DATETIME" }}
|
||||||
|
<div class="value">{{ if .LastDatapointValue.ValueDateTime.Valid }}{{ format_time .LastDatapointValue.Value }}{{ end }}</div>
|
||||||
|
{{ else }}
|
||||||
|
<div class="value">{{ .LastDatapointValue.Value }}</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ end }}
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
{{ block "page_label" . }}{{end}}
|
{{ block "page_label" . }}{{end}}
|
||||||
|
|
||||||
<form action="/trigger/update/{{ .Data.Trigger.ID }}" method="post">
|
<form id="form-trigger" action="/trigger/update/{{ .Data.Trigger.ID }}" method="post">
|
||||||
<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>
|
||||||
@ -21,7 +21,7 @@
|
|||||||
<div class="label">Datapoints</div>
|
<div class="label">Datapoints</div>
|
||||||
<div class="datapoints" style="margin-top: 4px">
|
<div class="datapoints" style="margin-top: 4px">
|
||||||
{{ range .Data.Datapoints }}
|
{{ range .Data.Datapoints }}
|
||||||
<div class="datapoint name">{{ .Name }}</div>
|
<div class="datapoint name"><b>{{ .Name }}</b></div>
|
||||||
<div class="datapoint value">{{ .LastDatapointValue.Value }}</div>
|
<div class="datapoint value">{{ .LastDatapointValue.Value }}</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
<div></div>
|
<div></div>
|
||||||
<div class="action">
|
<div class="action">
|
||||||
<button>Update</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>
|
||||||
|
Loading…
Reference in New Issue
Block a user