Compare commits
No commits in common. "1215b13d4746ee30fe9d95921f976ee717be29a9" and "5d24baedac98a0f034e38c694dc472e344cfa49c" have entirely different histories.
1215b13d47
...
5d24baedac
@ -120,7 +120,7 @@ Problems can be acknowledged if they are known problems that will not be current
|
|||||||
|
|
||||||
## Notifications
|
## Notifications
|
||||||
|
|
||||||
Smon has a couple of notification services (currently [[https://ntfy.sh|NTFY]], [[https://pushover.net|Pushover]] and script).
|
Smon has a couple of notification services (currently [[https://ntfy.sh|NTFY]] and script).
|
||||||
|
|
||||||
Services are added with a prio. The service with the lowest prio is tried first. \
|
Services are added with a prio. The service with the lowest prio is tried first. \
|
||||||
If sending through the service fails, the next service is tried and so on until one succeeds.
|
If sending through the service fails, the next service is tried and so on until one succeeds.
|
||||||
@ -134,10 +134,6 @@ What is sent isn't configurable (for now). What is sent is:
|
|||||||
|
|
||||||
An URL is provided to the topic which should receive the notifications.
|
An URL is provided to the topic which should receive the notifications.
|
||||||
|
|
||||||
## Pushover
|
|
||||||
|
|
||||||
The user key and API key for the Pushover application is needed. Additionally, a device key can be specified as well.
|
|
||||||
|
|
||||||
## Script
|
## Script
|
||||||
|
|
||||||
The script service is defined with a filename.
|
The script service is defined with a filename.
|
||||||
@ -165,4 +161,4 @@ A couple of small notes on development.
|
|||||||
* Add theme to select tag in `/views/pages/configuration.gotmpl`.
|
* Add theme to select tag in `/views/pages/configuration.gotmpl`.
|
||||||
* Create `/static/less/theme-<theme-name>.less`.
|
* Create `/static/less/theme-<theme-name>.less`.
|
||||||
* Create `/static/less/<theme-name>.less`.
|
* Create `/static/less/<theme-name>.less`.
|
||||||
* Copy a theme directory under `/static/images/` to the new name.
|
* Copy a theme directory under `/static/images/` to the new name.
|
4
main.go
4
main.go
@ -29,7 +29,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "v32"
|
const VERSION = "v31"
|
||||||
|
|
||||||
var (
|
var (
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
@ -1211,8 +1211,6 @@ func actionConfigurationNotificationUpdate(w http.ResponseWriter, r *http.Reques
|
|||||||
notificationManager.AddService(*svc)
|
notificationManager.AddService(*svc)
|
||||||
}
|
}
|
||||||
|
|
||||||
notificationManager.Reprioritize()
|
|
||||||
|
|
||||||
w.Header().Add("Location", "/configuration")
|
w.Header().Add("Location", "/configuration")
|
||||||
w.WriteHeader(302)
|
w.WriteHeader(302)
|
||||||
} // }}}
|
} // }}}
|
||||||
|
@ -24,16 +24,6 @@ func ServiceFactory(t string, config []byte, prio int, ackURL string, logger *sl
|
|||||||
}
|
}
|
||||||
ntfy.SetLogger(logger)
|
ntfy.SetLogger(logger)
|
||||||
return ntfy, nil
|
return ntfy, nil
|
||||||
|
|
||||||
case "PUSHOVER":
|
|
||||||
pushover, err := NewPushover(config, prio, ackURL)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithData(config)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pushover.SetLogger(logger)
|
|
||||||
return pushover, nil
|
|
||||||
|
|
||||||
case "SCRIPT":
|
case "SCRIPT":
|
||||||
script, err := NewScript(config, prio, ackURL)
|
script, err := NewScript(config, prio, ackURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -51,8 +41,6 @@ func NewInstance(typ string) Service {
|
|||||||
switch typ {
|
switch typ {
|
||||||
case "NTFY":
|
case "NTFY":
|
||||||
return new(NTFY)
|
return new(NTFY)
|
||||||
case "PUSHOVER":
|
|
||||||
return new(Pushover)
|
|
||||||
case "SCRIPT":
|
case "SCRIPT":
|
||||||
return new(Script)
|
return new(Script)
|
||||||
}
|
}
|
||||||
|
@ -39,9 +39,6 @@ func NewManager(logger *slog.Logger) (nm Manager) {
|
|||||||
func (nm *Manager) AddService(service Service) {
|
func (nm *Manager) AddService(service Service) {
|
||||||
service.SetExists(true)
|
service.SetExists(true)
|
||||||
nm.services = append(nm.services, service)
|
nm.services = append(nm.services, service)
|
||||||
}
|
|
||||||
|
|
||||||
func (nm *Manager) Reprioritize() {
|
|
||||||
slices.SortFunc(nm.services, func(a, b Service) int {
|
slices.SortFunc(nm.services, func(a, b Service) int {
|
||||||
if a.GetPrio() < b.GetPrio() {
|
if a.GetPrio() < b.GetPrio() {
|
||||||
return -1
|
return -1
|
||||||
|
@ -1,198 +0,0 @@
|
|||||||
package notification
|
|
||||||
|
|
||||||
import (
|
|
||||||
// External
|
|
||||||
werr "git.gibonuddevalla.se/go/wrappederror"
|
|
||||||
|
|
||||||
// Standard
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Pushover struct {
|
|
||||||
Description string
|
|
||||||
UserKey string `json:"user_key"`
|
|
||||||
APIKey string `json:"api_key"`
|
|
||||||
DeviceName string `json:"device_name"`
|
|
||||||
|
|
||||||
Prio int
|
|
||||||
AcknowledgeURL string
|
|
||||||
logger *slog.Logger
|
|
||||||
|
|
||||||
exists bool
|
|
||||||
updated Service
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
allServices = append(allServices, &Pushover{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPushover(config []byte, prio int, ackURL string) (instance *Pushover, err error) {
|
|
||||||
instance = new(Pushover)
|
|
||||||
err = json.Unmarshal(config, &instance)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithData(config)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
instance.Prio = prio
|
|
||||||
instance.AcknowledgeURL = ackURL
|
|
||||||
return instance, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) SetLogger(l *slog.Logger) {
|
|
||||||
po.logger = l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) GetType() string {
|
|
||||||
return "PUSHOVER"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) GetPrio() int {
|
|
||||||
return po.Prio
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) SetPrio(prio int) {
|
|
||||||
po.Prio = prio
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) SetExists(exists bool) {
|
|
||||||
po.exists = exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po Pushover) Exists() bool {
|
|
||||||
return po.exists
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) String() string {
|
|
||||||
if po.Description != "" {
|
|
||||||
return po.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("%s, %s", po.UserKey, po.APIKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po Pushover) Send(problemID int, msg []byte) (err error) {
|
|
||||||
var req *http.Request
|
|
||||||
var res *http.Response
|
|
||||||
|
|
||||||
pushoverRequest, _ := json.Marshal(map[string]string{
|
|
||||||
"token": po.APIKey,
|
|
||||||
"user": po.UserKey,
|
|
||||||
"device": po.DeviceName,
|
|
||||||
"message": string(msg),
|
|
||||||
})
|
|
||||||
|
|
||||||
req, err = http.NewRequest("POST", "https://api.pushover.net/1/messages.json", bytes.NewReader(pushoverRequest))
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithData(struct {
|
|
||||||
UserKey string
|
|
||||||
APIKey string
|
|
||||||
Msg []byte
|
|
||||||
}{
|
|
||||||
po.UserKey,
|
|
||||||
po.APIKey,
|
|
||||||
msg,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
//ackURL := fmt.Sprintf("http, OK, %s/notification/ack?problemID=%d", po.AcknowledgeURL, problemID)
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
|
||||||
res, err = http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
body, _ := io.ReadAll(res.Body)
|
|
||||||
poResp := struct {
|
|
||||||
Status int
|
|
||||||
Errors []string
|
|
||||||
}{}
|
|
||||||
err = json.Unmarshal(body, &poResp)
|
|
||||||
if err != nil {
|
|
||||||
err = werr.Wrap(err).WithData(body)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if poResp.Status != 1 {
|
|
||||||
err = werr.New("%s", strings.Join(poResp.Errors, ", "))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if res.StatusCode != 200 {
|
|
||||||
err = werr.New("Invalid Pushover response").WithData(body)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) Update(values url.Values) (err error) {
|
|
||||||
updated := Pushover{}
|
|
||||||
po.updated = &updated
|
|
||||||
|
|
||||||
// Prio
|
|
||||||
updated.Prio, err = strconv.Atoi(values.Get("prio"))
|
|
||||||
if err != nil {
|
|
||||||
return werr.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Description
|
|
||||||
updated.Description = strings.TrimSpace(values.Get("description"))
|
|
||||||
|
|
||||||
// API (application) key
|
|
||||||
givenAPIKey := values.Get("api_key")
|
|
||||||
if strings.TrimSpace(givenAPIKey) == "" {
|
|
||||||
return werr.New("API key cannot be empty")
|
|
||||||
}
|
|
||||||
updated.APIKey = strings.TrimSpace(givenAPIKey)
|
|
||||||
|
|
||||||
// User key
|
|
||||||
givenUserKey := values.Get("user_key")
|
|
||||||
if strings.TrimSpace(givenUserKey) == "" {
|
|
||||||
return werr.New("User key cannot be empty")
|
|
||||||
}
|
|
||||||
updated.UserKey = strings.TrimSpace(givenUserKey)
|
|
||||||
|
|
||||||
// Device name
|
|
||||||
updated.DeviceName = strings.TrimSpace(values.Get("device_name"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) Updated() Service {
|
|
||||||
return po.updated
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po *Pushover) Commit() {
|
|
||||||
updatedPushover := po.updated.(*Pushover)
|
|
||||||
po.Prio = updatedPushover.Prio
|
|
||||||
po.Description = updatedPushover.Description
|
|
||||||
po.APIKey = updatedPushover.APIKey
|
|
||||||
po.UserKey = updatedPushover.UserKey
|
|
||||||
po.DeviceName = updatedPushover.DeviceName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (po Pushover) JSON() []byte {
|
|
||||||
data := struct {
|
|
||||||
Description string
|
|
||||||
APIKey string `json:"api_key"`
|
|
||||||
UserKey string `json:"user_key"`
|
|
||||||
DeviceName string `json:"device_name"`
|
|
||||||
}{
|
|
||||||
po.Description,
|
|
||||||
po.APIKey,
|
|
||||||
po.UserKey,
|
|
||||||
po.DeviceName,
|
|
||||||
}
|
|
||||||
j, _ := json.Marshal(data)
|
|
||||||
return j
|
|
||||||
}
|
|
@ -101,7 +101,7 @@ func UpdateNotificationService(svc notification.Service) (created bool, err erro
|
|||||||
// Check if this is just a duplicated prio, which isn't allowed.
|
// Check if this is just a duplicated prio, which isn't allowed.
|
||||||
pgErr, isPgErr := err.(*pq.Error)
|
pgErr, isPgErr := err.(*pq.Error)
|
||||||
if isPgErr && pgErr.Code == "23505" {
|
if isPgErr && pgErr.Code == "23505" {
|
||||||
return false, werr.New("Prio %d is already used by another service", svc.Updated().GetPrio())
|
return false, werr.New("Prio %d is already used by another service", svc.GetPrio())
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, werr.Wrap(err).WithData(
|
return false, werr.Wrap(err).WithData(
|
||||||
|
@ -1 +0,0 @@
|
|||||||
ALTER TYPE notification_type ADD VALUE 'PUSHOVER';
|
|
@ -1,47 +0,0 @@
|
|||||||
{{ define "page" }}
|
|
||||||
<h1>Pushover</h1>
|
|
||||||
<style>
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: min-content 1fr;
|
|
||||||
grid-gap: 8px 16px;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=number] {
|
|
||||||
width: 64px;
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.grid > div {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<form action="/configuration/notification/update/{{ if .Data.Service.Exists }}{{ .Data.Service.GetPrio }}{{ else }}-1{{ end }}" method="post">
|
|
||||||
<input type="hidden" name="type" value="PUSHOVER">
|
|
||||||
<div class="grid">
|
|
||||||
<div>Prio:</div>
|
|
||||||
<input type="number" min=0 name="prio" value="{{ .Data.Service.GetPrio }}">
|
|
||||||
|
|
||||||
<div>Description</div>
|
|
||||||
<input type="text" name="description" value="{{ .Data.Service.Description }}" style="width: 100%">
|
|
||||||
|
|
||||||
<div>User key: <span class="error">*</span></div>
|
|
||||||
<input type="text" name="user_key" value="{{ .Data.Service.UserKey }}" style="width: 100%">
|
|
||||||
|
|
||||||
<div>API (application) key: <span class="error">*</span></div>
|
|
||||||
<input type="text" name="api_key" value="{{ .Data.Service.APIKey }}" style="width: 100%">
|
|
||||||
|
|
||||||
<div>Device name:</div>
|
|
||||||
<input type="text" name="device_name" value="{{ .Data.Service.DeviceName }}" style="width: 100%">
|
|
||||||
|
|
||||||
<button style="grid-column: 1 / -1; width: min-content;">OK</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{{ end }}
|
|
Loading…
Reference in New Issue
Block a user