diff --git a/helper.go b/helper.go index 99541ba..e8fcbe0 100644 --- a/helper.go +++ b/helper.go @@ -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 -} +}// }}} diff --git a/main.go b/main.go index 583ff95..5aa3192 100644 --- a/main.go +++ b/main.go @@ -709,31 +709,14 @@ 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" - } - + // Manage the values from the timefilter component var timeFrom, timeTo time.Time - 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")) + 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"), + ) // Fetch data point values according to the times. var values []DatapointValue @@ -743,6 +726,12 @@ 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", @@ -755,9 +744,11 @@ func pageDatapointValues(w http.ResponseWriter, r *http.Request, _ *session.T) { page.Data = map[string]any{ "Datapoint": datapoint, "Values": values, - "TimeFrom": timeFrom.Format("2006-01-02T15:04:05"), - "TimeTo": timeTo.Format("2006-01-02T15:04:05"), "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"), } page.Render(w, r) return @@ -770,14 +761,14 @@ func actionDatapointJson(w http.ResponseWriter, r *http.Request, _ *session.T) { return } - fromStr := r.URL.Query().Get("f") + fromStr := r.URL.Query().Get("time-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("t") + toStr := r.URL.Query().Get("time-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()) @@ -1198,27 +1189,14 @@ func actionConfigurationNotificationDelete(w http.ResponseWriter, r *http.Reques func pageNotifications(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{ var err error - // GET parameters. + // Manage the values from the timefilter component var timeFrom, timeTo time.Time - 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")) + 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"), + ) nss, err := notificationsSent(timeFrom, timeTo) if err != nil { @@ -1231,6 +1209,7 @@ 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"), }, diff --git a/sql/00023.sql b/sql/00023.sql new file mode 100644 index 0000000..b29ea09 --- /dev/null +++ b/sql/00023.sql @@ -0,0 +1 @@ +ALTER TABLE public.problem ADD COLUMN trigger_expression VARCHAR NOT NULL DEFAULT ''; diff --git a/static/css/default_light/datapoints.css b/static/css/default_light/datapoints.css index a534d2d..14a1aa5 100644 --- a/static/css/default_light/datapoints.css +++ b/static/css/default_light/datapoints.css @@ -99,25 +99,3 @@ .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; -} diff --git a/static/css/default_light/main.css b/static/css/default_light/main.css index 10baf58..4bae369 100644 --- a/static/css/default_light/main.css +++ b/static/css/default_light/main.css @@ -226,3 +226,37 @@ 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; +} diff --git a/static/css/default_light/notifications.css b/static/css/default_light/notifications.css index c747e0d..9dee5c0 100644 --- a/static/css/default_light/notifications.css +++ b/static/css/default_light/notifications.css @@ -1,37 +1,3 @@ -#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; } diff --git a/static/css/gruvbox/datapoints.css b/static/css/gruvbox/datapoints.css index c948a85..fdc0124 100644 --- a/static/css/gruvbox/datapoints.css +++ b/static/css/gruvbox/datapoints.css @@ -99,25 +99,3 @@ .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; -} diff --git a/static/css/gruvbox/main.css b/static/css/gruvbox/main.css index 98f785f..727b671 100644 --- a/static/css/gruvbox/main.css +++ b/static/css/gruvbox/main.css @@ -226,3 +226,37 @@ 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; +} diff --git a/static/css/gruvbox/notifications.css b/static/css/gruvbox/notifications.css index 60db03b..1cc900e 100644 --- a/static/css/gruvbox/notifications.css +++ b/static/css/gruvbox/notifications.css @@ -1,37 +1,3 @@ -#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; } diff --git a/static/js/datapoint_values.js b/static/js/datapoint_values.js index 20d0e54..82b0ff7 100644 --- a/static/js/datapoint_values.js +++ b/static/js/datapoint_values.js @@ -1,13 +1,7 @@ -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() +function selectDisplay(display) { + const inputDisplay = document.getElementById('input-display') + inputDisplay.value = display + inputDisplay.form.submit() } class Graph { @@ -61,7 +55,9 @@ class Dataset { constructor(id, initialData) { this.datapointID = id this.values = {} - initialData.forEach(v=>this.values[v.ID] = v) + if (initialData === null) + return + initialData.forEach(v => this.values[v.ID] = v) } xValues() { @@ -76,7 +72,7 @@ class Dataset { return fetch(`/datapoint/json/${this.datapointID}?f=${from}&t=${to}`) .then(data => data.json()) .then(datapointValues => { - datapointValues.forEach(dp=>{ + datapointValues.forEach(dp => { this.values[dp.ID] = dp }) document.getElementById('num-values').innerText = Object.keys(this.values).length diff --git a/static/less/datapoints.less b/static/less/datapoints.less index 8391a00..8491560 100644 --- a/static/less/datapoints.less +++ b/static/less/datapoints.less @@ -120,30 +120,3 @@ 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; - } -} diff --git a/static/less/main.less b/static/less/main.less index aa11833..689ca8f 100644 --- a/static/less/main.less +++ b/static/less/main.less @@ -280,3 +280,46 @@ 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; + } + } +} + diff --git a/static/less/notifications.less b/static/less/notifications.less index 00f7db7..48a9614 100644 --- a/static/less/notifications.less +++ b/static/less/notifications.less @@ -1,47 +1,5 @@ @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; } diff --git a/timefilter.go b/timefilter.go new file mode 100644 index 0000000..8d221db --- /dev/null +++ b/timefilter.go @@ -0,0 +1,41 @@ +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 +} diff --git a/views/components/timefilter.gotmpl b/views/components/timefilter.gotmpl new file mode 100644 index 0000000..8742015 --- /dev/null +++ b/views/components/timefilter.gotmpl @@ -0,0 +1,61 @@ +{{ define "timefilter" }} + + + +
+ + + +
+
From
+
To
+ + + + + +
+
Presets
+
Offsets
+ + + +
+
Hour
+
+ + + +
+
Day
+
+ + + +
+
Week
+
+ + + +
+
Month
+
+
+ + +
+
+{{ end }} diff --git a/views/pages/notifications.gotmpl b/views/pages/notifications.gotmpl index 2bf8e8a..d99776a 100644 --- a/views/pages/notifications.gotmpl +++ b/views/pages/notifications.gotmpl @@ -10,61 +10,9 @@ 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() - } -
- - - -
-
From
-
To
- - - -
-
Presets
-
Offsets
- - - -
-
Hour
-
- - - -
-
Day
-
- - - -
-
Week
-
- - - -
-
Month
-
-
- -
-
+ {{ block "timefilter" . }}{{ end }}
Sent