Compare commits

..

No commits in common. "332788dd20807538c8494b67d844061e6d198feb" and "0f69874475990627ec97d307764c36a7d7ab3396" have entirely different histories.

19 changed files with 366 additions and 322 deletions

View File

@ -63,7 +63,7 @@ func applyTimeOffset(t time.Time, duration time.Duration, amountStr string) time
return t.Add(duration * time.Duration(amount))
} // }}}
func presetTimeInterval(duration time.Duration, presetStr string, timeFrom, timeTo *time.Time) () {// {{{
func presetTimeInterval(duration time.Duration, presetStr string, timeFrom, timeTo *time.Time) () {
if presetStr == "" {
return
}
@ -73,4 +73,4 @@ func presetTimeInterval(duration time.Duration, presetStr string, timeFrom, time
(*timeFrom) = now.Add(duration * -1 * time.Duration(presetTime))
(*timeTo) = now
return
}// }}}
}

95
main.go
View File

@ -29,7 +29,7 @@ import (
"time"
)
const VERSION = "v29"
const VERSION = "v28"
var (
logger *slog.Logger
@ -521,21 +521,7 @@ func pageProblems(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
CONFIG: smonConfig.Settings,
}
// Manage the values from the timefilter component
var err error
var timeFrom, timeTo time.Time
timeFrom, timeTo, err = timefilterParse(
r.URL.Query().Get("time-f"),
r.URL.Query().Get("time-t"),
r.URL.Query().Get("time-offset"),
r.URL.Query().Get("time-preset"),
)
if err != nil {
httpError(w, err)
return
}
problems, err := ProblemsRetrieve(true, timeFrom, timeTo)
problems, err := ProblemsRetrieve()
if err != nil {
httpError(w, werr.Wrap(err).Log())
return
@ -560,10 +546,6 @@ func pageProblems(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
page.Data = map[string]any{
"Problems": problems,
"ProblemsGrouped": problemsGrouped,
"TimeSubmit": "/problems",
"TimeFrom": timeFrom.Format("2006-01-02T15:04:05"),
"TimeTo": timeTo.Format("2006-01-02T15:04:05"),
}
page.Render(w, r)
return
@ -727,14 +709,31 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) {
return
}
// Manage the values from the timefilter component
// GET parameters.
display := r.URL.Query().Get("display")
if display == "" && datapoint.Datatype == INT {
display = "graph"
}
var timeFrom, timeTo time.Time
timeFrom, timeTo, err = timefilterParse(
r.URL.Query().Get("time-f"),
r.URL.Query().Get("time-t"),
r.URL.Query().Get("time-offset"),
r.URL.Query().Get("time-preset"),
)
yesterday := time.Now().Add(time.Duration(-24 * time.Hour))
timeFrom, err = parseHTMLDateTime(r.URL.Query().Get("f"), yesterday)
if err != nil {
httpError(w, werr.Wrap(err).WithData(r.URL.Query().Get("f")).Log())
return
}
timeTo, err = parseHTMLDateTime(r.URL.Query().Get("t"), time.Now())
if err != nil {
httpError(w, werr.Wrap(err).WithData(r.URL.Query().Get("t")).Log())
return
}
presetTimeInterval(time.Hour, r.URL.Query().Get("preset"), &timeFrom, &timeTo)
// Apply an optionally set offset (in seconds).
timeFrom = applyTimeOffset(timeFrom, time.Second, r.URL.Query().Get("offset-time"))
timeTo = applyTimeOffset(timeTo, time.Second, r.URL.Query().Get("offset-time"))
// Fetch data point values according to the times.
var values []DatapointValue
@ -744,12 +743,6 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) {
return
}
// GET parameters.
display := r.URL.Query().Get("display")
if display == "" && datapoint.Datatype == INT {
display = "graph"
}
page := Page{
LAYOUT: "main",
PAGE: "datapoint_values",
@ -762,11 +755,9 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) {
page.Data = map[string]any{
"Datapoint": datapoint,
"Values": values,
"Display": display,
"TimeSubmit": "/datapoint/values/" + strconv.Itoa(datapoint.ID),
"TimeFrom": timeFrom.Format("2006-01-02T15:04:05"),
"TimeTo": timeTo.Format("2006-01-02T15:04:05"),
"Display": display,
}
page.Render(w, r)
return
@ -779,14 +770,14 @@ func actionDatapointJson(w http.ResponseWriter, r *http.Request, _ *session.T) {
return
}
fromStr := r.URL.Query().Get("time-f")
fromStr := r.URL.Query().Get("f")
from, err := time.ParseInLocation("2006-01-02 15:04:05", fromStr[0:min(19, len(fromStr))], smonConfig.Timezone())
if err != nil {
httpError(w, werr.Wrap(err).Log())
return
}
toStr := r.URL.Query().Get("time-t")
toStr := r.URL.Query().Get("t")
to, err := time.ParseInLocation("2006-01-02 15:04:05", toStr[0:min(19, len(toStr))], smonConfig.Timezone())
if err != nil {
httpError(w, werr.Wrap(err).Log())
@ -1207,14 +1198,27 @@ func actionConfigurationNotificationDelete(w http.ResponseWriter, r *http.Reques
func pageNotifications(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
var err error
// Manage the values from the timefilter component
// GET parameters.
var timeFrom, timeTo time.Time
timeFrom, timeTo, err = timefilterParse(
r.URL.Query().Get("time-f"),
r.URL.Query().Get("time-t"),
r.URL.Query().Get("time-offset"),
r.URL.Query().Get("time-preset"),
)
lastWeek := time.Now().Add(time.Duration(-7 * 24 * time.Hour))
timeFrom, err = parseHTMLDateTime(r.URL.Query().Get("f"), lastWeek)
if err != nil {
httpError(w, werr.Wrap(err).Log())
return
}
timeTo, err = parseHTMLDateTime(r.URL.Query().Get("t"), time.Now())
if err != nil {
httpError(w, werr.Wrap(err).Log())
return
}
presetTimeInterval(time.Hour, r.URL.Query().Get("preset"), &timeFrom, &timeTo)
// Apply an optionally set offset (in seconds).
timeFrom = applyTimeOffset(timeFrom, time.Second, r.URL.Query().Get("offset-time"))
timeTo = applyTimeOffset(timeTo, time.Second, r.URL.Query().Get("offset-time"))
nss, err := notificationsSent(timeFrom, timeTo)
if err != nil {
@ -1227,7 +1231,6 @@ func pageNotifications(w http.ResponseWriter, r *http.Request, _ *session.T) { /
CONFIG: smonConfig.Settings,
Data: map[string]any{
"Notifications": nss,
"TimeSubmit": "/notifications",
"TimeFrom": timeFrom.Format("2006-01-02T15:04:05"),
"TimeTo": timeTo.Format("2006-01-02T15:04:05"),
},

View File

@ -2,7 +2,7 @@ package main
import (
// External
werr "git.gibonuddevalla.se/go/wrappederror"
we "git.gibonuddevalla.se/go/wrappederror"
// Standard
"database/sql"
@ -16,7 +16,7 @@ import (
type Problem struct { // {{{
ID int
Start time.Time
End time.Time
End sql.NullTime
Acknowledged bool
Datapoints map[string]any
DatapointValues map[string]any `json:"datapoints"`
@ -26,7 +26,7 @@ type Problem struct { // {{{
SectionName string `json:"section_name"`
} // }}}
func ProblemsRetrieve(archived bool, from, to time.Time) (problems []Problem, err error) { // {{{
func ProblemsRetrieve() (problems []Problem, err error) { // {{{
problems = []Problem{}
row := service.Db.Conn.QueryRow(`
SELECT
@ -35,7 +35,7 @@ func ProblemsRetrieve(archived bool, from, to time.Time) (problems []Problem, er
(SELECT
p.id,
p.start,
TO_CHAR(p.end, 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM') AS end,
p.end,
p.acknowledged,
p.datapoints,
t.id AS trigger_id,
@ -47,13 +47,7 @@ func ProblemsRetrieve(archived bool, from, to time.Time) (problems []Problem, er
INNER JOIN section s ON t.section_id = s.id
INNER JOIN area a ON s.area_id = a.id
WHERE
CASE
WHEN $1 THEN true
WHEN NOT $1 THEN p.end IS NULL
END AND
p.start >= $2 AND
p.end <= $3
p.end IS NULL
ORDER BY
p.start DESC)
@ -77,16 +71,13 @@ func ProblemsRetrieve(archived bool, from, to time.Time) (problems []Problem, er
dp.nodata_is_problem
ORDER BY
dp.name ASC)
) AS problems`,
archived,
from,
to,
)
) AS problems
`)
var jsonBody []byte
err = row.Scan(&jsonBody)
if err != nil {
err = werr.Wrap(err)
err = we.Wrap(err)
return
}
@ -96,7 +87,7 @@ func ProblemsRetrieve(archived bool, from, to time.Time) (problems []Problem, er
err = json.Unmarshal(jsonBody, &problems)
if err != nil {
err = werr.Wrap(err)
err = we.Wrap(err)
}
return
} // }}}
@ -114,22 +105,17 @@ func ProblemStart(trigger Trigger) (problemID int, err error) { // {{{
var openProblems int
err = row.Scan(&openProblems)
if err != nil && err != sql.ErrNoRows {
err = werr.Wrap(err).WithData(trigger.ID)
err = we.Wrap(err).WithData(trigger.ID)
return
}
// Open up a new problem if no open exists.
if openProblems == 0 {
datapointValuesJson, _ := json.Marshal(trigger.DatapointValues)
row = service.Db.Conn.QueryRow(
`INSERT INTO problem(trigger_id, datapoints, trigger_expression) VALUES($1, $2, $3) RETURNING id`,
trigger.ID,
datapointValuesJson,
trigger.Expression,
)
row = service.Db.Conn.QueryRow(`INSERT INTO problem(trigger_id, datapoints) VALUES($1, $2) RETURNING id`, trigger.ID, datapointValuesJson)
err = row.Scan(&problemID)
if err != nil {
err = werr.Wrap(err).WithData(trigger)
err = we.Wrap(err).WithData(trigger)
}
}
return
@ -144,7 +130,7 @@ func ProblemClose(trigger Trigger) (problemID int, err error) { // {{{
}
if err != nil {
err = werr.Wrap(err).WithData(trigger)
err = we.Wrap(err).WithData(trigger)
return
}
return
@ -152,13 +138,13 @@ func ProblemClose(trigger Trigger) (problemID int, err error) { // {{{
func ProblemAcknowledge(id int, state bool) (err error) { // {{{
_, err = service.Db.Conn.Exec(`UPDATE problem SET "acknowledged"=$2 WHERE id=$1`, id, state)
if err != nil {
err = werr.Wrap(err).WithData(id)
err = we.Wrap(err).WithData(id)
return
}
return
} // }}}
func (p Problem) FormattedValues() string { // {{{
func (p Problem) FormattedValues() string {
out := []string{}
for key, val := range p.DatapointValues {
var keyval string
@ -188,7 +174,4 @@ func (p Problem) FormattedValues() string { // {{{
sort.Strings(out)
return strings.Join(out, "\n")
} // }}}
func (p Problem) IsArchived() bool { // {{{
return !p.End.IsZero()
} // }}}
}

View File

@ -1 +0,0 @@
ALTER TABLE public.problem ADD COLUMN trigger_expression VARCHAR NOT NULL DEFAULT '';

View File

@ -99,3 +99,25 @@
.graph #graph-values {
height: calc(100vh - 416px);
}
.time-offset {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
}
.time-offset .header-1 {
font-weight: bold;
justify-self: start;
}
.time-offset .header-2 {
font-weight: bold;
justify-self: start;
grid-column: 2 / -1;
}
.time-offset .preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}

View File

@ -226,37 +226,3 @@ label {
width: min-content;
border-radius: 8px;
}
#time-selector {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
width: min-content;
border-radius: 6px;
}
#time-selector button {
width: 100px;
margin-top: 12px;
justify-self: end;
}
#time-selector #time-filter {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
}
#time-selector #time-filter .header-1 {
font-weight: bold;
justify-self: start;
}
#time-selector #time-filter .header-2 {
font-weight: bold;
justify-self: start;
grid-column: 2 / -1;
}
#time-selector #time-filter .preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}

View File

@ -1,3 +1,37 @@
#time-select {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
width: min-content;
border-radius: 6px;
}
#time-select button {
width: 100px;
margin-top: 12px;
justify-self: end;
}
#time-select #time-offsets {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
}
#time-select #time-offsets .header-1 {
font-weight: bold;
justify-self: start;
}
#time-select #time-offsets .header-2 {
font-weight: bold;
justify-self: start;
grid-column: 2 / -1;
}
#time-select #time-offsets .preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}
input[type="datetime-local"] {
padding: 6px;
}

View File

@ -99,3 +99,25 @@
.graph #graph-values {
height: calc(100vh - 416px);
}
.time-offset {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
}
.time-offset .header-1 {
font-weight: bold;
justify-self: start;
}
.time-offset .header-2 {
font-weight: bold;
justify-self: start;
grid-column: 2 / -1;
}
.time-offset .preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}

View File

@ -226,37 +226,3 @@ label {
width: min-content;
border-radius: 8px;
}
#time-selector {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
width: min-content;
border-radius: 6px;
}
#time-selector button {
width: 100px;
margin-top: 12px;
justify-self: end;
}
#time-selector #time-filter {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
}
#time-selector #time-filter .header-1 {
font-weight: bold;
justify-self: start;
}
#time-selector #time-filter .header-2 {
font-weight: bold;
justify-self: start;
grid-column: 2 / -1;
}
#time-selector #time-filter .preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}

View File

@ -1,3 +1,37 @@
#time-select {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
width: min-content;
border-radius: 6px;
}
#time-select button {
width: 100px;
margin-top: 12px;
justify-self: end;
}
#time-select #time-offsets {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
}
#time-select #time-offsets .header-1 {
font-weight: bold;
justify-self: start;
}
#time-select #time-offsets .header-2 {
font-weight: bold;
justify-self: start;
grid-column: 2 / -1;
}
#time-select #time-offsets .preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}
input[type="datetime-local"] {
padding: 6px;
}

View File

@ -1,7 +1,13 @@
function selectDisplay(display) {
const inputDisplay = document.getElementById('input-display')
inputDisplay.value = display
inputDisplay.form.submit()
function preset(hours) {
const inputPreset = document.querySelector('input[name="preset"]')
inputPreset.value = hours
inputPreset.form.submit()
}
function offsetTime(seconds) {
const inputPreset = document.querySelector('input[name="offset-time"]')
inputPreset.value = seconds
inputPreset.form.submit()
}
class Graph {
@ -55,8 +61,6 @@ class Dataset {
constructor(id, initialData) {
this.datapointID = id
this.values = {}
if (initialData === null)
return
initialData.forEach(v=>this.values[v.ID] = v)
}

View File

@ -120,3 +120,30 @@
height: calc(100vh - 416px);
}
}
.time-offset {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
.header-1 {
font-weight: bold;
justify-self: start;
}
.header-2 {
font-weight: bold;
justify-self: start;
grid-column: ~"2 / -1";
}
.preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}
}

View File

@ -280,46 +280,3 @@ label {
width: min-content;
border-radius: 8px;
}
#time-selector {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
width: min-content;
border-radius: 6px;
button {
width: 100px;
margin-top: 12px;
justify-self: end;
}
#time-filter {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
.header-1 {
font-weight: bold;
justify-self: start;
}
.header-2 {
font-weight: bold;
justify-self: start;
grid-column: ~"2 / -1";
}
.preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}
}
}

View File

@ -1,5 +1,47 @@
@import "theme-@{THEME}.less";
#time-select {
display: grid;
grid-template-columns: min-content min-content;
grid-gap: 6px 16px;
width: min-content;
border-radius: 6px;
button {
width: 100px;
margin-top: 12px;
justify-self: end;
}
#time-offsets {
display: grid;
grid-template-columns: min-content repeat(3, min-content);
grid-gap: 16px;
margin-top: 16px;
align-items: center;
justify-items: center;
.header-1 {
font-weight: bold;
justify-self: start;
}
.header-2 {
font-weight: bold;
justify-self: start;
grid-column: ~"2 / -1";
}
.preset {
white-space: nowrap;
justify-self: start;
padding-right: 32px;
}
}
}
input[type="datetime-local"] {
padding: 6px;
}

View File

@ -1,41 +0,0 @@
package main
import (
// External
werr "git.gibonuddevalla.se/go/wrappederror"
// Standard
"time"
)
func timefilterParse(timeFromStr, timeToStr, offset, preset string) (timeFrom, timeTo time.Time, err error) {
if preset != "" {
presetTimeInterval(time.Hour, preset, &timeFrom, &timeTo)
return
}
yesterday := time.Now().Add(time.Duration(-24 * time.Hour))
timeFrom, err = parseHTMLDateTime(timeFromStr, yesterday)
if err != nil {
err = werr.Wrap(err).WithData(timeFromStr)
return
}
timeTo, err = parseHTMLDateTime(timeToStr, time.Now())
if err != nil {
err = werr.Wrap(err).WithData(timeToStr)
return
}
// Protect the user from switching from/to dates, leading to
// zero matching values from the database.
if timeFrom.After(timeTo) {
timeFrom, timeTo = timeTo, timeFrom
}
// Apply an optionally set offset (in seconds).
timeFrom = applyTimeOffset(timeFrom, time.Second, offset)
timeTo = applyTimeOffset(timeTo, time.Second, offset)
return
}

View File

@ -1,61 +0,0 @@
{{ define "timefilter" }}
<script type="text/javascript">
function preset(hours) {
const inputPreset = document.querySelector('input[name="time-preset"]')
inputPreset.value = hours
inputPreset.form.submit()
}
function offsetTime(seconds) {
const el = document.querySelector('input[name="time-offset"]')
el.value = seconds
el.form.submit()
}
</script>
<form action="{{ .Data.TimeSubmit }}" method="get" id="form-time-selector">
<input type="hidden" name="time-preset" value="">
<input type="hidden" name="time-offset" value=0>
<div id="time-selector">
<div>From</div>
<div>To</div>
<input name="time-f" value="{{ .Data.TimeFrom }}" type="datetime-local">
<input name="time-t" value="{{ .Data.TimeTo }}" type="datetime-local">
<div id="time-filter">
<div class="header-1">Presets</div>
<div class="header-2">Offsets</div>
<div class="preset"><a href="#" onclick="preset(1)">Last hour</a></div>
<div><a href="#" onclick="offsetTime(-3600)">◀</a></div>
<div>Hour</div>
<div><a href="#" onclick="offsetTime(3600)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24)">Last 24 hours</a></div>
<div><a href="#" onclick="offsetTime(-86400)">◀</a></div>
<div>Day</div>
<div><a href="#" onclick="offsetTime(86400)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24 * 7)">Last 7 days</a></div>
<div><a href="#" onclick="offsetTime(-7 * 86400)">◀</a></div>
<div>Week</div>
<div><a href="#" onclick="offsetTime(7 * 86400)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24 * 31)">Last 31 days</a></div>
<div><a href="#" onclick="offsetTime(-31 * 86400)">◀</a></div>
<div>Month</div>
<div><a href="#" onclick="offsetTime(31 * 86400)">▶</a></div>
</div>
<button>OK</button>
</div>
</form>
{{ end }}

View File

@ -7,20 +7,56 @@
{{ block "page_label" . }}{{end}}
<div style="margin-bottom: 16px">
<input onchange="selectDisplay('graph')" name="display" type="radio" id="display-graph" {{ if $graph }} checked {{ end}}> <label for="display-graph">Graph</label>
<input onchange="selectDisplay('list')" name="display" type="radio" id="display-list" {{ if not $graph }} checked {{ end }}> <label for="display-list">List</label>
<form action="/datapoint/values/{{ .Data.Datapoint.ID }}" method="get" style="margin-top: -16px">
<input type="hidden" name="preset" value="">
<input type="hidden" name="offset-time" value=0>
{{ if eq .Data.Datapoint.Datatype "INT" }}
<div>
<input name="display" value="graph" type="radio" id="display-graph" {{ if $graph }} checked {{ end}}> <label for="display-graph">Graph</label>
<input name="display" value="list" type="radio" id="display-list" {{ if not $graph }} checked {{ end }}> <label for="display-list">List</label>
</div>
{{ end }}
<div class="value-selector">
<div>Values from</div>
<div>Values to</div>
<input name="f" type="datetime-local" step="1" value="{{ .Data.TimeFrom }}">
<input name="t" type="datetime-local" step="1" value="{{ .Data.TimeTo }}">
<div class="time-offset">
<div class="header-1">Presets</div>
<div class="header-2">Offsets</div>
<div class="preset"><a href="#" onclick="preset(1)">Last hour</a></div>
<div><a href="#" onclick="offsetTime(-3600)">◀</a></div>
<div>Hour</div>
<div><a href="#" onclick="offsetTime(3600)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24)">Last 24 hours</a></div>
<div><a href="#" onclick="offsetTime(-86400)">◀</a></div>
<div>Day</div>
<div><a href="#" onclick="offsetTime(86400)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24 * 7)">Last 7 days</a></div>
<div><a href="#" onclick="offsetTime(-7 * 86400)">◀</a></div>
<div>Week</div>
<div><a href="#" onclick="offsetTime(7 * 86400)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24 * 31)">Last 31 days</a></div>
<div><a href="#" onclick="offsetTime(-31 * 86400)">◀</a></div>
<div>Month</div>
<div><a href="#" onclick="offsetTime(31 * 86400)">▶</a></div>
</div>
<button>OK</button>
</div>
{{ block "timefilter" . }}{{ end }}
<script type="text/javascript">
const inputDisplay = document.createElement('input')
inputDisplay.id = 'input-display'
inputDisplay.name = 'display'
inputDisplay.type = 'hidden'
document.getElementById('form-time-selector').append(inputDisplay);
</script>
</form>
{{ if $graph }}
<div class="graph">
@ -36,6 +72,7 @@
{{ .Data.Datapoint.ID }},
{{ .Data.Values }},
)
</script>
{{ else }}
<div id="values">

View File

@ -10,9 +10,61 @@
evt.target.close()
}
}
function preset(hours) {
const inputPreset = document.querySelector('input[name="preset"]')
inputPreset.value = hours
inputPreset.form.submit()
}
function offsetTime(seconds) {
const el = document.querySelector('input[name="offset-time"]')
el.value = seconds
el.form.submit()
}
</script>
{{ block "timefilter" . }}{{ end }}
<form action="/notifications" method="get">
<input type="hidden" name="preset" value="">
<input type="hidden" name="offset-time" value="">
<div id="time-select">
<div>From</div>
<div>To</div>
<input name="f" value="{{ .Data.TimeFrom }}" type="datetime-local">
<input name="t" value="{{ .Data.TimeTo }}" type="datetime-local">
<div id="time-offsets">
<div class="header-1">Presets</div>
<div class="header-2">Offsets</div>
<div class="preset"><a href="#" onclick="preset(1)">Last hour</a></div>
<div><a href="#" onclick="offsetTime(-3600)">◀</a></div>
<div>Hour</div>
<div><a href="#" onclick="offsetTime(3600)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24)">Last 24 hours</a></div>
<div><a href="#" onclick="offsetTime(-86400)">◀</a></div>
<div>Day</div>
<div><a href="#" onclick="offsetTime(86400)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24 * 7)">Last 7 days</a></div>
<div><a href="#" onclick="offsetTime(-7 * 86400)">◀</a></div>
<div>Week</div>
<div><a href="#" onclick="offsetTime(7 * 86400)">▶</a></div>
<div class="preset"><a href="#" onclick="preset(24 * 31)">Last 31 days</a></div>
<div><a href="#" onclick="offsetTime(-31 * 86400)">◀</a></div>
<div>Month</div>
<div><a href="#" onclick="offsetTime(31 * 86400)">▶</a></div>
</div>
<button>OK</button>
</div>
</form>
<div id="notifications">
<div class="header">Sent</div>

View File

@ -10,13 +10,11 @@
{{ block "page_label" . }}{{end}}
<div style="margin-bottom: 16px">
<div>
<input type="radio" name="display" id="display-table" onclick="_ui.displayAreas()"> <label for="display-table">Areas</label>
<input type="radio" name="display" id="display-list" onclick="_ui.displayList()"> <label for="display-list">List</label>
</div>
{{ block "timefilter" . }}{{ end }}
<div class="display-list hidden">
<div id="problems-list">
<div style="grid-column: 1/-1;"><h2>Current</h2></div>