Time filtering of notifications
This commit is contained in:
parent
b0ffce05f0
commit
17a22caa5d
42
main.go
42
main.go
@ -1198,7 +1198,45 @@ func actionConfigurationNotificationDelete(w http.ResponseWriter, r *http.Reques
|
|||||||
} // }}}
|
} // }}}
|
||||||
|
|
||||||
func pageNotifications(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
func pageNotifications(w http.ResponseWriter, r *http.Request, _ *session.T) { // {{{
|
||||||
nss, err := notificationsSent()
|
var err error
|
||||||
|
|
||||||
|
// GET parameters.
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var presetTime int
|
||||||
|
now := time.Now()
|
||||||
|
presetStr := r.URL.Query().Get("preset")
|
||||||
|
if presetStr != "" {
|
||||||
|
presetTime, err = strconv.Atoi(presetStr)
|
||||||
|
if err != nil {
|
||||||
|
pageError(w, "/notifications", werr.Wrap(err).WithData(presetStr).Log())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
timeFrom = now.Add(time.Hour * -1 * time.Duration(presetTime))
|
||||||
|
timeTo = now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply an optionally set offset (in seconds).
|
||||||
|
var offsetTime int
|
||||||
|
offsetTimeStr := r.URL.Query().Get("offset-time")
|
||||||
|
offsetTime, err = strconv.Atoi(offsetTimeStr)
|
||||||
|
timeFrom = timeFrom.Add(time.Second * time.Duration(offsetTime))
|
||||||
|
timeTo = timeTo.Add(time.Second * time.Duration(offsetTime))
|
||||||
|
|
||||||
|
nss, err := notificationsSent(timeFrom, timeTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pageError(w, "/", werr.Wrap(err).Log())
|
pageError(w, "/", werr.Wrap(err).Log())
|
||||||
}
|
}
|
||||||
@ -1209,6 +1247,8 @@ func pageNotifications(w http.ResponseWriter, r *http.Request, _ *session.T) { /
|
|||||||
CONFIG: smonConfig.Settings,
|
CONFIG: smonConfig.Settings,
|
||||||
Data: map[string]any{
|
Data: map[string]any{
|
||||||
"Notifications": nss,
|
"Notifications": nss,
|
||||||
|
"TimeFrom": timeFrom.Format("2006-01-02T15:04:05"),
|
||||||
|
"TimeTo": timeTo.Format("2006-01-02T15:04:05"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ func notificationLog(notificationService *notification.Service, problemID int, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func notificationsSent() (nss []NotificationSend, err error) {
|
func notificationsSent(from, to time.Time) (nss []NotificationSend, err error) {
|
||||||
var rows *sqlx.Rows
|
var rows *sqlx.Rows
|
||||||
rows, err = service.Db.Conn.Queryx(
|
rows, err = service.Db.Conn.Queryx(
|
||||||
`
|
`
|
||||||
@ -57,15 +57,20 @@ func notificationsSent() (nss []NotificationSend, err error) {
|
|||||||
ns.error::varchar,
|
ns.error::varchar,
|
||||||
ns.acknowledged
|
ns.acknowledged
|
||||||
|
|
||||||
FROM
|
FROM public.notification_send ns
|
||||||
public.notification_send ns
|
|
||||||
INNER JOIN notification n ON ns.notification_id = n.id
|
INNER JOIN notification n ON ns.notification_id = n.id
|
||||||
INNER JOIN problem p ON ns.problem_id = p.id
|
INNER JOIN problem p ON ns.problem_id = p.id
|
||||||
INNER JOIN "trigger" t ON p.trigger_id = t.id
|
INNER JOIN "trigger" t ON p.trigger_id = t.id
|
||||||
|
WHERE
|
||||||
|
ns.send >= $1 AND
|
||||||
|
ns.send <= $2
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
send DESC
|
send DESC
|
||||||
`)
|
`,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = werr.Wrap(err)
|
err = werr.Wrap(err)
|
||||||
return
|
return
|
||||||
|
@ -40,11 +40,16 @@ button:focus {
|
|||||||
#areas .area .section .name {
|
#areas .area .section .name {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
dialog {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
dialog,
|
||||||
#datapoints,
|
#datapoints,
|
||||||
#problems-list,
|
#problems-list,
|
||||||
#acknowledged-list,
|
#acknowledged-list,
|
||||||
#values,
|
#values,
|
||||||
#services {
|
#services,
|
||||||
|
#notifications {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.25);
|
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.25);
|
||||||
|
@ -1,3 +1,42 @@
|
|||||||
|
#time-select {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content min-content;
|
||||||
|
grid-gap: 6px 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid #7bb8eb;
|
||||||
|
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;
|
||||||
|
}
|
||||||
#notifications {
|
#notifications {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, min-content);
|
grid-template-columns: repeat(5, min-content);
|
||||||
@ -18,3 +57,9 @@
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #7bb8eb;
|
color: #7bb8eb;
|
||||||
}
|
}
|
||||||
|
#notifications .ok {
|
||||||
|
color: #0a0;
|
||||||
|
}
|
||||||
|
#notifications .error {
|
||||||
|
color: #a00;
|
||||||
|
}
|
||||||
|
@ -40,11 +40,16 @@ button:focus {
|
|||||||
#areas .area .section .name {
|
#areas .area .section .name {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
dialog {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
dialog,
|
||||||
#datapoints,
|
#datapoints,
|
||||||
#problems-list,
|
#problems-list,
|
||||||
#acknowledged-list,
|
#acknowledged-list,
|
||||||
#values,
|
#values,
|
||||||
#services {
|
#services,
|
||||||
|
#notifications {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.25);
|
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.25);
|
||||||
|
@ -1,3 +1,42 @@
|
|||||||
|
#time-select {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content min-content;
|
||||||
|
grid-gap: 6px 16px;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid #777;
|
||||||
|
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;
|
||||||
|
}
|
||||||
#notifications {
|
#notifications {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, min-content);
|
grid-template-columns: repeat(5, min-content);
|
||||||
@ -18,3 +57,9 @@
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
color: #777;
|
color: #777;
|
||||||
}
|
}
|
||||||
|
#notifications .ok {
|
||||||
|
color: #0a0;
|
||||||
|
}
|
||||||
|
#notifications .error {
|
||||||
|
color: #a00;
|
||||||
|
}
|
||||||
|
@ -58,7 +58,11 @@ button {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#datapoints, #problems-list, #acknowledged-list, #values, #services {
|
dialog {
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog, #datapoints, #problems-list, #acknowledged-list, #values, #services, #notifications {
|
||||||
background-color: #fff !important;
|
background-color: #fff !important;
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
box-shadow: 5px 5px 8px 0px rgba(0,0,0,0.25);
|
box-shadow: 5px 5px 8px 0px rgba(0,0,0,0.25);
|
||||||
|
@ -1,5 +1,54 @@
|
|||||||
@import "theme-@{THEME}.less";
|
@import "theme-@{THEME}.less";
|
||||||
|
|
||||||
|
#time-select {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: min-content min-content;
|
||||||
|
grid-gap: 6px 16px;
|
||||||
|
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid @text3;
|
||||||
|
width: min-content;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
//grid-column: ~"1 / -1";
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
#notifications {
|
#notifications {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, min-content);
|
grid-template-columns: repeat(5, min-content);
|
||||||
@ -21,4 +70,12 @@
|
|||||||
font-weight: @bold;
|
font-weight: @bold;
|
||||||
color: @text3;
|
color: @text3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ok {
|
||||||
|
color: #0a0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #a00;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,67 @@
|
|||||||
{{ $version := .VERSION }}
|
{{ $version := .VERSION }}
|
||||||
{{ $theme := .CONFIG.THEME }}
|
{{ $theme := .CONFIG.THEME }}
|
||||||
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/{{ .CONFIG.THEME }}/notifications.css">
|
<link rel="stylesheet" type="text/css" href="/css/{{ .VERSION }}/{{ .CONFIG.THEME }}/notifications.css">
|
||||||
|
<script type="text/javascript">
|
||||||
|
function dialogClick(evt) {
|
||||||
|
if (evt.target.tagName.toUpperCase() == 'DIALOG') {
|
||||||
|
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>
|
||||||
|
|
||||||
|
<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 * 7 * 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 id="notifications">
|
||||||
<div class="header">Sent</div>
|
<div class="header">Sent</div>
|
||||||
@ -13,10 +74,24 @@
|
|||||||
<div class="header">Error</div>
|
<div class="header">Error</div>
|
||||||
{{ range .Data.Notifications }}
|
{{ range .Data.Notifications }}
|
||||||
<div>{{ format_time .Sent }}</div>
|
<div>{{ format_time .Sent }}</div>
|
||||||
<div>{{ if .OK }}✔{{ else }}✗{{ end }}</div>
|
<div>{{ if .OK }}<span class="ok">✔</span>{{ else }}<span class="error">✗</span>{{ end }}</div>
|
||||||
<div>{{ .TriggerName }}</div>
|
<div>{{ .TriggerName }}</div>
|
||||||
<div>{{ .Prio }}:{{ .Service }}</div>
|
<div>{{ .Prio }}:{{ .Service }}</div>
|
||||||
<div><pre>{{ if .Error.Valid }}{{ .ErrorIndented }}{{ end }}</pre></div>
|
<div>
|
||||||
|
{{ if .Error.Valid }}
|
||||||
|
<img src="/images/{{ $version }}/{{ $theme }}/info-filled.svg" onclick="document.getElementById('error-{{ .ID }}').showModal()">
|
||||||
|
<dialog id="error-{{ .ID }}" onclick="dialogClick(event)">
|
||||||
|
<div style="padding: 16px 32px">
|
||||||
|
<pre>{{ .ErrorIndented }}</pre>
|
||||||
|
<div style="text-align: center">
|
||||||
|
<button onclick="document.getElementById('error-{{ .ID }}').close()">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
{{ else }}
|
||||||
|
<img src="/images/{{ $version }}/{{ $theme }}/info-outline.svg">
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user