From 257a4968ec17889c582742843bba21ed000bfc55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 4 Jul 2024 19:21:02 +0200 Subject: [PATCH 1/5] Added Pushover notification --- README.md | 8 +- notification/factory.go | 12 ++ notification/pushover.go | 183 +++++++++++++++++++++++ sql/00025.sql | 1 + views/pages/notification/pushover.gotmpl | 44 ++++++ 5 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 notification/pushover.go create mode 100644 sql/00025.sql create mode 100644 views/pages/notification/pushover.gotmpl diff --git a/README.md b/README.md index b922964..193705b 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ Problems can be acknowledged if they are known problems that will not be current ## Notifications -Smon has a couple of notification services (currently [[https://ntfy.sh|NTFY]] and script). +Smon has a couple of notification services (currently [[https://ntfy.sh|NTFY]], [[https://pushover.net|Pushover]] and script). 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. @@ -134,6 +134,10 @@ What is sent isn't configurable (for now). What is sent is: 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 The script service is defined with a filename. @@ -161,4 +165,4 @@ A couple of small notes on development. * Add theme to select tag in `/views/pages/configuration.gotmpl`. * Create `/static/less/theme-.less`. * Create `/static/less/.less`. -* Copy a theme directory under `/static/images/` to the new name. \ No newline at end of file +* Copy a theme directory under `/static/images/` to the new name. diff --git a/notification/factory.go b/notification/factory.go index 16c35d4..1506b43 100644 --- a/notification/factory.go +++ b/notification/factory.go @@ -24,6 +24,16 @@ func ServiceFactory(t string, config []byte, prio int, ackURL string, logger *sl } ntfy.SetLogger(logger) 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": script, err := NewScript(config, prio, ackURL) if err != nil { @@ -41,6 +51,8 @@ func NewInstance(typ string) Service { switch typ { case "NTFY": return new(NTFY) + case "PUSHOVER": + return new(Pushover) case "SCRIPT": return new(Script) } diff --git a/notification/pushover.go b/notification/pushover.go new file mode 100644 index 0000000..9b35232 --- /dev/null +++ b/notification/pushover.go @@ -0,0 +1,183 @@ +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 { + 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 { + 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 + + updated.Prio, err = strconv.Atoi(values.Get("prio")) + if err != nil { + return werr.Wrap(err) + } + + givenAPIKey := values.Get("api_key") + if strings.TrimSpace(givenAPIKey) == "" { + return werr.New("API key cannot be empty") + } + updated.APIKey = strings.TrimSpace(givenAPIKey) + + givenUserKey := values.Get("user_key") + if strings.TrimSpace(givenUserKey) == "" { + return werr.New("User key cannot be empty") + } + updated.UserKey = strings.TrimSpace(givenUserKey) + + 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.APIKey = updatedPushover.APIKey + po.UserKey = updatedPushover.UserKey + po.DeviceName = updatedPushover.DeviceName +} + +func (po Pushover) JSON() []byte { + data := struct { + APIKey string `json:"api_key"` + UserKey string `json:"user_key"` + DeviceName string `json:"device_name"` + }{ + po.APIKey, + po.UserKey, + po.DeviceName, + } + j, _ := json.Marshal(data) + return j +} diff --git a/sql/00025.sql b/sql/00025.sql new file mode 100644 index 0000000..8981022 --- /dev/null +++ b/sql/00025.sql @@ -0,0 +1 @@ +ALTER TYPE notification_type ADD VALUE 'PUSHOVER'; diff --git a/views/pages/notification/pushover.gotmpl b/views/pages/notification/pushover.gotmpl new file mode 100644 index 0000000..e483b03 --- /dev/null +++ b/views/pages/notification/pushover.gotmpl @@ -0,0 +1,44 @@ +{{ define "page" }} +

Pushover

+ + +
+ +
+
Prio:
+ + +
User key:
+ + +
API (application) key:
+ + +
Device name (optional):
+ + + +
+
+{{ end }} From 414ca0a95c094589d9706339d8e9388a22649029 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 4 Jul 2024 19:36:56 +0200 Subject: [PATCH 2/5] Reprioritize notification services when updated. --- main.go | 2 ++ notification/pkg.go | 3 +++ 2 files changed, 5 insertions(+) diff --git a/main.go b/main.go index dfca0a2..a02a485 100644 --- a/main.go +++ b/main.go @@ -1211,6 +1211,8 @@ func actionConfigurationNotificationUpdate(w http.ResponseWriter, r *http.Reques notificationManager.AddService(*svc) } + notificationManager.Reprioritize() + w.Header().Add("Location", "/configuration") w.WriteHeader(302) } // }}} diff --git a/notification/pkg.go b/notification/pkg.go index efa6712..948d7b3 100644 --- a/notification/pkg.go +++ b/notification/pkg.go @@ -39,6 +39,9 @@ func NewManager(logger *slog.Logger) (nm Manager) { func (nm *Manager) AddService(service Service) { service.SetExists(true) nm.services = append(nm.services, service) +} + +func (nm *Manager) Reprioritize() { slices.SortFunc(nm.services, func(a, b Service) int { if a.GetPrio() < b.GetPrio() { return -1 From 06f88f697c300685827c29e107b46d3212478d7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 4 Jul 2024 19:37:18 +0200 Subject: [PATCH 3/5] Added description to Pushover notification service. --- notification/pushover.go | 27 ++++++++++++++++++------ views/pages/notification/pushover.gotmpl | 9 +++++--- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/notification/pushover.go b/notification/pushover.go index 9b35232..023ff9d 100644 --- a/notification/pushover.go +++ b/notification/pushover.go @@ -17,9 +17,10 @@ import ( ) type Pushover struct { - UserKey string `json:"user_key"` - APIKey string `json:"api_key"` - DeviceName string `json:"device_name"` + Description string + UserKey string `json:"user_key"` + APIKey string `json:"api_key"` + DeviceName string `json:"device_name"` Prio int AcknowledgeURL string @@ -70,6 +71,10 @@ func (po Pushover) Exists() bool { } func (po *Pushover) String() string { + if po.Description != "" { + return po.Description + } + return fmt.Sprintf("%s, %s", po.UserKey, po.APIKey) } @@ -135,23 +140,30 @@ 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 } @@ -163,6 +175,7 @@ func (po *Pushover) Updated() Service { 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 @@ -170,10 +183,12 @@ func (po *Pushover) Commit() { func (po Pushover) JSON() []byte { data := struct { - APIKey string `json:"api_key"` - UserKey string `json:"user_key"` - DeviceName string `json:"device_name"` + 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, diff --git a/views/pages/notification/pushover.gotmpl b/views/pages/notification/pushover.gotmpl index e483b03..f4dd438 100644 --- a/views/pages/notification/pushover.gotmpl +++ b/views/pages/notification/pushover.gotmpl @@ -29,13 +29,16 @@ div.grid > div {
Prio:
-
User key:
+
Description
+ + +
User key: *
-
API (application) key:
+
API (application) key: *
-
Device name (optional):
+
Device name:
From e10783ec548a276165788246570b9742aea11494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 4 Jul 2024 19:38:36 +0200 Subject: [PATCH 4/5] Fixed error message when trying to change prio to a used number. --- notification_manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notification_manager.go b/notification_manager.go index 1690d8e..28afa62 100644 --- a/notification_manager.go +++ b/notification_manager.go @@ -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. pgErr, isPgErr := err.(*pq.Error) if isPgErr && pgErr.Code == "23505" { - return false, werr.New("Prio %d is already used by another service", svc.GetPrio()) + return false, werr.New("Prio %d is already used by another service", svc.Updated().GetPrio()) } return false, werr.Wrap(err).WithData( From 1215b13d4746ee30fe9d95921f976ee717be29a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magnus=20=C3=85hall?= Date: Thu, 4 Jul 2024 19:48:05 +0200 Subject: [PATCH 5/5] Bumped to v32 --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index a02a485..3a93ed8 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ import ( "time" ) -const VERSION = "v31" +const VERSION = "v32" var ( logger *slog.Logger