Better alert status flow, add resolved message
This commit is contained in:
parent
ed71f7fb13
commit
4c19e89c1c
|
|
@ -9,6 +9,7 @@
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"sqlx",
|
||||||
"tgbotapi"
|
"tgbotapi"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,8 @@ export const AlertFormModal = ({ alert, open, onClose }: Props) => {
|
||||||
triggerInterval: 0,
|
triggerInterval: 0,
|
||||||
...(alert && {
|
...(alert && {
|
||||||
name: alert.name,
|
name: alert.name,
|
||||||
contactPointId: alert.contactPointId,
|
|
||||||
customMessage: alert.customMessage,
|
customMessage: alert.customMessage,
|
||||||
|
contactPointId: alert.contactPointId,
|
||||||
triggerInterval: alert.triggerInterval,
|
triggerInterval: alert.triggerInterval,
|
||||||
}),
|
}),
|
||||||
...(parsedCondition && {
|
...(parsedCondition && {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) StartAlerts() {
|
func (s *Server) StartAlerts() {
|
||||||
ticker := time.NewTicker(time.Second * 10)
|
ticker := time.NewTicker(time.Second * 1)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
err := s.Services.Alerts.EvaluateAlerts()
|
err := s.Services.AlertsEvaluator.EvaluateAlerts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error evaluating alerts: ", err)
|
fmt.Println("Error evaluating alerts: ", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,12 @@ import (
|
||||||
"basic-sensor-receiver/config"
|
"basic-sensor-receiver/config"
|
||||||
"basic-sensor-receiver/database"
|
"basic-sensor-receiver/database"
|
||||||
"basic-sensor-receiver/services"
|
"basic-sensor-receiver/services"
|
||||||
"database/sql"
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
DB *sql.DB
|
DB *sqlx.DB
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
Services *services.Services
|
Services *services.Services
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Initialize(databaseUrl string) (*sql.DB, error) {
|
func Initialize(databaseUrl string) (*sqlx.DB, error) {
|
||||||
db, err := sql.Open("sqlite3", databaseUrl)
|
db, err := sqlx.Open("sqlite3", databaseUrl)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed migrations/*.sql
|
//go:embed migrations/*.sql
|
||||||
var migrationStorage embed.FS
|
var migrationStorage embed.FS
|
||||||
|
|
||||||
func getAppliedMigrationsSet(db *sql.DB) (map[string]bool, error) {
|
func getAppliedMigrationsSet(db *sqlx.DB) (map[string]bool, error) {
|
||||||
set := make(map[string]bool)
|
set := make(map[string]bool)
|
||||||
|
|
||||||
rows, err := db.Query("SELECT id FROM migrations")
|
rows, err := db.Query("SELECT id FROM migrations")
|
||||||
|
|
@ -56,10 +57,6 @@ func getMigrations() ([]string, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(items)
|
sort.Strings(items)
|
||||||
|
|
||||||
return items, nil
|
return items, nil
|
||||||
|
|
@ -75,7 +72,7 @@ func getMigrationContent(id string) (string, error) {
|
||||||
return string(content), nil
|
return string(content), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPendingMigrations(db *sql.DB) ([]string, error) {
|
func getPendingMigrations(db *sqlx.DB) ([]string, error) {
|
||||||
var migrationsToRun []string
|
var migrationsToRun []string
|
||||||
|
|
||||||
migrationSet, err := getAppliedMigrationsSet(db)
|
migrationSet, err := getAppliedMigrationsSet(db)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ require (
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
|
||||||
github.com/goccy/go-json v0.9.10 // indirect
|
github.com/goccy/go-json v0.9.10 // indirect
|
||||||
github.com/golobby/cast v1.3.0 // indirect
|
github.com/golobby/cast v1.3.0 // indirect
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh
|
||||||
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw=
|
||||||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
|
||||||
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM=
|
||||||
|
|
@ -27,6 +28,8 @@ github.com/golobby/dotenv v1.3.1 h1:BvQyNuOQITmIXNHpQ/FUG2gZcUGmcGMyODMeUfiKkeU=
|
||||||
github.com/golobby/dotenv v1.3.1/go.mod h1:EWUdOzuDlA1g4hdjo++WD37DhNZw33Oce8ryH3liZTQ=
|
github.com/golobby/dotenv v1.3.1/go.mod h1:EWUdOzuDlA1g4hdjo++WD37DhNZw33Oce8ryH3liZTQ=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
||||||
|
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
||||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
|
@ -39,8 +42,10 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
||||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ type ContactPointMessageType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ContactPointEventAlertTriggered = "alert_triggered"
|
ContactPointEventAlertTriggered = "alert_triggered"
|
||||||
|
ContactPointEventAlertResolved = "alert_resolved"
|
||||||
ContactPointEventTest = "test"
|
ContactPointEventTest = "test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -13,6 +14,7 @@ type ContactPointEvent struct {
|
||||||
Type ContactPointMessageType
|
Type ContactPointMessageType
|
||||||
|
|
||||||
AlertTriggeredEvent *ContactPointAlertTriggeredEvent
|
AlertTriggeredEvent *ContactPointAlertTriggeredEvent
|
||||||
|
AlertResolvedEvent *ContactPointAlertResolvedEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContactPointAlertTriggeredEvent struct {
|
type ContactPointAlertTriggeredEvent struct {
|
||||||
|
|
@ -23,6 +25,14 @@ type ContactPointAlertTriggeredEvent struct {
|
||||||
LastValue float64
|
LastValue float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ContactPointAlertResolvedEvent struct {
|
||||||
|
Alert *models.AlertItem
|
||||||
|
Sensor *models.SensorItem
|
||||||
|
SensorValueCondition *models.AlertConditionSensorValue
|
||||||
|
SensorLastContactCondition *models.AlertConditionSensorLastContact
|
||||||
|
LastValue float64
|
||||||
|
}
|
||||||
|
|
||||||
type ContactPointIntegration interface {
|
type ContactPointIntegration interface {
|
||||||
ProcessEvent(evt *ContactPointEvent, rawConfig string) error
|
ProcessEvent(evt *ContactPointEvent, rawConfig string) error
|
||||||
ValidateConfig(rawConfig string) error
|
ValidateConfig(rawConfig string) error
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package integrations
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
|
@ -35,18 +36,71 @@ func (s TelegramIntegration) ProcessEvent(evt *ContactPointEvent, rawConfig stri
|
||||||
case ContactPointEventAlertTriggered:
|
case ContactPointEventAlertTriggered:
|
||||||
data := evt.AlertTriggeredEvent
|
data := evt.AlertTriggeredEvent
|
||||||
|
|
||||||
text := fmt.Sprintf("🚨 %s is at {value}", data.Sensor.Name)
|
if data.SensorValueCondition != nil {
|
||||||
|
text := fmt.Sprintf("🚨 %s is at {value}", data.Sensor.Name)
|
||||||
|
|
||||||
if data.Alert.CustomMessage != "" {
|
if data.Alert.CustomMessage != "" {
|
||||||
text = data.Alert.CustomMessage
|
text = data.Alert.CustomMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
text = strings.Replace(text, "{value}", strconv.FormatFloat(data.LastValue, 'f', -1, 64), -1)
|
||||||
|
|
||||||
|
msg := tgbotapi.NewMessage(config.TargetChannel, text)
|
||||||
|
msg.ParseMode = "Markdown"
|
||||||
|
_, err = bot.Send(msg)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
text = strings.Replace(text, "{value}", strconv.FormatFloat(data.LastValue, 'f', -1, 64), -1)
|
if data.SensorLastContactCondition != nil {
|
||||||
|
text := fmt.Sprintf("🚨 %s has not reported in for last %s %s", data.Sensor.Name, strconv.FormatFloat(data.SensorLastContactCondition.Value, 'f', -1, 64), data.SensorLastContactCondition.ValueUnit)
|
||||||
|
if data.Alert.CustomMessage != "" {
|
||||||
|
text = data.Alert.CustomMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := tgbotapi.NewMessage(config.TargetChannel, text)
|
||||||
|
msg.ParseMode = "Markdown"
|
||||||
|
_, err = bot.Send(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("No condition found for alert triggered event")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case ContactPointEventAlertResolved:
|
||||||
|
data := evt.AlertResolvedEvent
|
||||||
|
|
||||||
|
if data.SensorValueCondition != nil {
|
||||||
|
text := fmt.Sprintf("✅ %s is at {value}", data.Sensor.Name)
|
||||||
|
|
||||||
|
if data.Alert.CustomMessage != "" {
|
||||||
|
text = data.Alert.CustomMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
text = strings.Replace(text, "{value}", strconv.FormatFloat(data.LastValue, 'f', -1, 64), -1)
|
||||||
|
|
||||||
|
msg := tgbotapi.NewMessage(config.TargetChannel, text)
|
||||||
|
msg.ParseMode = "Markdown"
|
||||||
|
_, err = bot.Send(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.SensorLastContactCondition != nil {
|
||||||
|
text := fmt.Sprintf("✅ %s has reported in last %s %s", data.Sensor.Name, strconv.FormatFloat(data.SensorLastContactCondition.Value, 'f', -1, 64), data.SensorLastContactCondition.ValueUnit)
|
||||||
|
if data.Alert.CustomMessage != "" {
|
||||||
|
text = data.Alert.CustomMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := tgbotapi.NewMessage(config.TargetChannel, text)
|
||||||
|
msg.ParseMode = "Markdown"
|
||||||
|
_, err = bot.Send(msg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("No condition found for alert triggered event")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
msg := tgbotapi.NewMessage(config.TargetChannel, text)
|
|
||||||
msg.ParseMode = "Markdown"
|
|
||||||
_, err = bot.Send(msg)
|
|
||||||
return err
|
|
||||||
case ContactPointEventTest:
|
case ContactPointEventTest:
|
||||||
msg := tgbotapi.NewMessage(config.TargetChannel, "Test message from Basic Sensor Receiver")
|
msg := tgbotapi.NewMessage(config.TargetChannel, "Test message from Basic Sensor Receiver")
|
||||||
_, err = bot.Send(msg)
|
_, err = bot.Send(msg)
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type AlertItem struct {
|
type AlertItem struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id" db:"id"`
|
||||||
ContactPointId int64 `json:"contactPointId"`
|
ContactPointId int64 `json:"contactPointId" db:"contact_point_id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name" db:"name"`
|
||||||
Condition string `json:"condition"`
|
Condition string `json:"condition" db:"condition"`
|
||||||
CustomMessage string `json:"customMessage"`
|
CustomMessage string `json:"customMessage" db:"custom_message"`
|
||||||
|
|
||||||
/* how long does the condition have to be true for the alert to go off */
|
/* how long does the condition have to be true for the alert to go off */
|
||||||
TriggerInterval int64 `json:"triggerInterval"`
|
TriggerInterval int64 `json:"triggerInterval" db:"trigger_interval"`
|
||||||
|
|
||||||
/* current alert status, possible values: good, pending, alerting */
|
/* current alert status, possible values: good, pending, alerting */
|
||||||
LastStatus string `json:"lastStatus"`
|
LastStatus AlertStatus `json:"lastStatus" db:"last_status"`
|
||||||
/* time at which was status last changed */
|
/* time at which was status last changed */
|
||||||
LastStatusAt int64 `json:"lastStatusAt"`
|
LastStatusAt int64 `json:"lastStatusAt" db:"last_status_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlertConditionSensorValue struct {
|
type AlertConditionSensorValue struct {
|
||||||
|
|
@ -27,3 +27,19 @@ type AlertConditionSensorLastContact struct {
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
ValueUnit string `json:"valueUnit"`
|
ValueUnit string `json:"valueUnit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AlertConditionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlertConditionSensorValueType = "sensor_value"
|
||||||
|
AlertConditionSensorLastContactType = "sensor_last_contact"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlertStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AlertStatusOk = "ok"
|
||||||
|
AlertStatusAlertPending = "alert_pending"
|
||||||
|
AlertStatusAlerting = "alerting"
|
||||||
|
AlertStatusOkPending = "ok_pending"
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,213 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"basic-sensor-receiver/integrations"
|
||||||
|
"basic-sensor-receiver/models"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AlertsEvaluatorService struct {
|
||||||
|
ctx *Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func evaluateSensorValueCondition(condition *models.AlertConditionSensorValue, value *sensorValue) bool {
|
||||||
|
switch condition.Condition {
|
||||||
|
case "less":
|
||||||
|
return value.Value < condition.Value
|
||||||
|
case "more":
|
||||||
|
return value.Value > condition.Value
|
||||||
|
case "equal":
|
||||||
|
return value.Value == condition.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AlertsEvaluatorService) EvaluateAlerts() error {
|
||||||
|
alerts, err := s.ctx.Services.Alerts.GetList()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, alert := range alerts {
|
||||||
|
err := s.EvaluateAlert(&alert)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func unitToSeconds(unit string) float64 {
|
||||||
|
switch unit {
|
||||||
|
case "s":
|
||||||
|
return 1
|
||||||
|
case "m":
|
||||||
|
return 60
|
||||||
|
case "h":
|
||||||
|
return 3600
|
||||||
|
case "d":
|
||||||
|
return 86400
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
|
||||||
|
condition := map[string]interface{}{}
|
||||||
|
err := json.Unmarshal([]byte(alert.Condition), &condition)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This flow is a bit cumbersome, consider refactoring
|
||||||
|
newStatus := alert.LastStatus
|
||||||
|
lastValue := float64(0)
|
||||||
|
conditionMet := false
|
||||||
|
sensorId := int64(-1)
|
||||||
|
var sensorValueCondition *models.AlertConditionSensorValue
|
||||||
|
var sensorLastContactCondition *models.AlertConditionSensorLastContact
|
||||||
|
|
||||||
|
switch condition["type"].(string) {
|
||||||
|
case models.AlertConditionSensorValueType:
|
||||||
|
{
|
||||||
|
sensorValueCondition = &models.AlertConditionSensorValue{
|
||||||
|
SensorId: int64(condition["sensorId"].(float64)),
|
||||||
|
Condition: condition["condition"].(string),
|
||||||
|
Value: condition["value"].(float64),
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix())
|
||||||
|
lastValue = value.Value
|
||||||
|
sensorId = int64(sensorValueCondition.SensorId)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
lastValue = float64(0)
|
||||||
|
conditionMet = false
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("error getting sensor value: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
conditionMet = evaluateSensorValueCondition(sensorValueCondition, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case models.AlertConditionSensorLastContactType:
|
||||||
|
{
|
||||||
|
sensorLastContactCondition = &models.AlertConditionSensorLastContact{
|
||||||
|
SensorId: int64(condition["sensorId"].(float64)),
|
||||||
|
Value: condition["value"].(float64),
|
||||||
|
ValueUnit: condition["valueUnit"].(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
|
||||||
|
lastValue = float64(value.Timestamp)
|
||||||
|
sensorId = sensorLastContactCondition.SensorId
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
lastValue = float64(0)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("error getting sensor last contact value: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit))
|
||||||
|
|
||||||
|
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Status flow:
|
||||||
|
ok -> alert_pending
|
||||||
|
alert_pending -> alerting OR alert_pending -> ok
|
||||||
|
alerting -> ok_pending
|
||||||
|
ok_pending -> ok OR ok_pending -> alerting
|
||||||
|
*/
|
||||||
|
if conditionMet {
|
||||||
|
if alert.LastStatus == models.AlertStatusOk {
|
||||||
|
newStatus = models.AlertStatusAlertPending
|
||||||
|
} else if alert.LastStatus == models.AlertStatusAlertPending {
|
||||||
|
if time.Now().Unix()-alert.LastStatusAt >= alert.TriggerInterval {
|
||||||
|
newStatus = models.AlertStatusAlerting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if alert.LastStatus == models.AlertStatusAlerting {
|
||||||
|
newStatus = models.AlertStatusOkPending
|
||||||
|
} else if alert.LastStatus == models.AlertStatusOkPending {
|
||||||
|
if time.Now().Unix()-alert.LastStatusAt >= alert.TriggerInterval {
|
||||||
|
newStatus = models.AlertStatusOk
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if newStatus != alert.LastStatus || newStatus == models.AlertStatusOk {
|
||||||
|
s.ctx.DB.Exec("UPDATE alerts SET last_status = ?, last_status_at = ? WHERE id = ?", newStatus, time.Now().Unix(), alert.Id)
|
||||||
|
|
||||||
|
if newStatus == models.AlertStatusAlerting || newStatus == models.AlertStatusOk {
|
||||||
|
sensor, err := s.ctx.Services.Sensors.GetById(sensorId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting sensor detail (%d): %v", sensorId, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contactPoint, err := s.ctx.Services.ContactPoints.GetById(alert.ContactPointId)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting contact point detail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchService, err := contactPoint.getService(s.ctx)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting dispatch service: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch alert resolved event when we are transitioning ok_pending -> ok
|
||||||
|
if newStatus == models.AlertStatusOk && alert.LastStatus == models.AlertStatusOkPending {
|
||||||
|
err = dispatchService.ProcessEvent(&integrations.ContactPointEvent{
|
||||||
|
Type: integrations.ContactPointEventAlertResolved,
|
||||||
|
AlertResolvedEvent: &integrations.ContactPointAlertResolvedEvent{
|
||||||
|
Alert: alert,
|
||||||
|
Sensor: sensor,
|
||||||
|
SensorValueCondition: sensorValueCondition,
|
||||||
|
SensorLastContactCondition: sensorLastContactCondition,
|
||||||
|
LastValue: lastValue,
|
||||||
|
},
|
||||||
|
}, contactPoint.TypeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dispatch alert triggered event when we are transitioning alert_pending -> alerting
|
||||||
|
if newStatus == models.AlertStatusAlerting && alert.LastStatus == models.AlertStatusAlertPending {
|
||||||
|
err = dispatchService.ProcessEvent(&integrations.ContactPointEvent{
|
||||||
|
Type: integrations.ContactPointEventAlertTriggered,
|
||||||
|
AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{
|
||||||
|
Alert: alert,
|
||||||
|
Sensor: sensor,
|
||||||
|
SensorValueCondition: sensorValueCondition,
|
||||||
|
SensorLastContactCondition: sensorLastContactCondition,
|
||||||
|
LastValue: lastValue,
|
||||||
|
},
|
||||||
|
}, contactPoint.TypeConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error dispatching alert: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"basic-sensor-receiver/integrations"
|
|
||||||
"basic-sensor-receiver/models"
|
"basic-sensor-receiver/models"
|
||||||
"encoding/json"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -11,194 +9,15 @@ type AlertsService struct {
|
||||||
ctx *Context
|
ctx *Context
|
||||||
}
|
}
|
||||||
|
|
||||||
func evaluateSensorValueCondition(condition *models.AlertConditionSensorValue, value *sensorValue) bool {
|
func (s *AlertsService) GetList() ([]models.AlertItem, error) {
|
||||||
switch condition.Condition {
|
alerts := []models.AlertItem{}
|
||||||
case "less":
|
|
||||||
return value.Value < condition.Value
|
|
||||||
case "more":
|
|
||||||
return value.Value > condition.Value
|
|
||||||
case "equal":
|
|
||||||
return value.Value == condition.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
err := s.ctx.DB.Select(&alerts, `
|
||||||
}
|
SELECT id, contact_point_id, name, custom_message, condition, trigger_interval, last_status, last_status_at
|
||||||
|
FROM alerts
|
||||||
|
ORDER BY name ASC
|
||||||
|
`)
|
||||||
|
|
||||||
func (s *AlertsService) EvaluateAlerts() error {
|
|
||||||
alerts, err := s.GetList()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, alert := range alerts {
|
|
||||||
err := s.EvaluateAlert(alert)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func unitToSeconds(unit string) float64 {
|
|
||||||
switch unit {
|
|
||||||
case "s":
|
|
||||||
return 1
|
|
||||||
case "m":
|
|
||||||
return 60
|
|
||||||
case "h":
|
|
||||||
return 3600
|
|
||||||
case "d":
|
|
||||||
return 86400
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AlertsService) EvaluateAlert(alert *models.AlertItem) error {
|
|
||||||
condition := map[string]interface{}{}
|
|
||||||
err := json.Unmarshal([]byte(alert.Condition), &condition)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
newStatus := alert.LastStatus
|
|
||||||
lastValue := float64(0)
|
|
||||||
conditionMet := false
|
|
||||||
sensorId := int64(-1)
|
|
||||||
sensorValueCondition := models.AlertConditionSensorValue{}
|
|
||||||
sensorLastContactCondition := models.AlertConditionSensorLastContact{}
|
|
||||||
|
|
||||||
switch condition["type"].(string) {
|
|
||||||
case "sensor_value":
|
|
||||||
{
|
|
||||||
sensorValueCondition = models.AlertConditionSensorValue{
|
|
||||||
SensorId: int64(condition["sensorId"].(float64)),
|
|
||||||
Condition: condition["condition"].(string),
|
|
||||||
Value: condition["value"].(float64),
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix())
|
|
||||||
lastValue = value.Value
|
|
||||||
sensorId = int64(sensorValueCondition.SensorId)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
conditionMet = evaluateSensorValueCondition(&sensorValueCondition, value)
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
case "sensor_last_contact":
|
|
||||||
{
|
|
||||||
sensorLastContactCondition := models.AlertConditionSensorLastContact{
|
|
||||||
SensorId: int64(condition["sensorId"].(float64)),
|
|
||||||
Value: condition["value"].(float64),
|
|
||||||
ValueUnit: condition["valueUnit"].(string),
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
|
|
||||||
lastValue = float64(value.Timestamp)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit))
|
|
||||||
|
|
||||||
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
|
|
||||||
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if conditionMet {
|
|
||||||
if alert.LastStatus == "good" {
|
|
||||||
newStatus = "pending"
|
|
||||||
} else if alert.LastStatus == "pending" {
|
|
||||||
if time.Now().Unix()-alert.LastStatusAt >= alert.TriggerInterval {
|
|
||||||
newStatus = "alerting"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if alert.LastStatus == "alerting" {
|
|
||||||
newStatus = "pending"
|
|
||||||
} else if alert.LastStatus == "pending" {
|
|
||||||
if time.Now().Unix()-alert.LastStatusAt >= alert.TriggerInterval {
|
|
||||||
newStatus = "good"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if newStatus != alert.LastStatus {
|
|
||||||
s.ctx.DB.Exec("UPDATE alerts SET last_status = ?, last_status_at = ? WHERE id = ?", newStatus, time.Now().Unix(), alert.Id)
|
|
||||||
|
|
||||||
if newStatus == "alerting" {
|
|
||||||
sensor, err := s.ctx.Services.Sensors.GetById(sensorId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
contactPoint, err := s.ctx.Services.ContactPoints.GetById(alert.ContactPointId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchService, err := contactPoint.getService(s.ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = dispatchService.ProcessEvent(&integrations.ContactPointEvent{
|
|
||||||
Type: integrations.ContactPointEventAlertTriggered,
|
|
||||||
AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{
|
|
||||||
Alert: alert,
|
|
||||||
Sensor: sensor,
|
|
||||||
SensorValueCondition: &sensorValueCondition,
|
|
||||||
SensorLastContactCondition: &sensorLastContactCondition,
|
|
||||||
LastValue: lastValue,
|
|
||||||
},
|
|
||||||
}, contactPoint.TypeConfig)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *AlertsService) GetList() ([]*models.AlertItem, error) {
|
|
||||||
rows, err := s.ctx.DB.Query("SELECT id, contact_point_id, name, condition, trigger_interval, last_status, last_status_at FROM alerts ORDER BY name ASC")
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
alerts := []*models.AlertItem{}
|
|
||||||
|
|
||||||
for rows.Next() {
|
|
||||||
alert := models.AlertItem{}
|
|
||||||
|
|
||||||
err := rows.Scan(&alert.Id, &alert.ContactPointId, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
alerts = append(alerts, &alert)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rows.Err()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -209,8 +28,14 @@ func (s *AlertsService) GetList() ([]*models.AlertItem, error) {
|
||||||
func (s *AlertsService) GetById(id int64) (*models.AlertItem, error) {
|
func (s *AlertsService) GetById(id int64) (*models.AlertItem, error) {
|
||||||
alert := models.AlertItem{}
|
alert := models.AlertItem{}
|
||||||
|
|
||||||
row := s.ctx.DB.QueryRow("SELECT id, contact_point_id, name, condition, trigger_interval, last_status, last_status_at FROM alerts WHERE id = ?", id)
|
err := s.ctx.DB.Get(&alert,
|
||||||
err := row.Scan(&alert.Id, &alert.ContactPointId, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt)
|
`
|
||||||
|
SELECT id, contact_point_id, name, custom_message, condition, trigger_interval, last_status, last_status_at
|
||||||
|
FROM alerts
|
||||||
|
WHERE id = $1
|
||||||
|
`,
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -226,13 +51,17 @@ func (s *AlertsService) Create(contactPointId int64, name string, condition stri
|
||||||
Condition: condition,
|
Condition: condition,
|
||||||
TriggerInterval: triggerInterval,
|
TriggerInterval: triggerInterval,
|
||||||
CustomMessage: customMessage,
|
CustomMessage: customMessage,
|
||||||
LastStatus: "good",
|
LastStatus: models.AlertStatusOk,
|
||||||
LastStatusAt: 0,
|
LastStatusAt: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := s.ctx.DB.Exec(
|
res, err := s.ctx.DB.NamedExec(
|
||||||
"INSERT INTO alerts (contact_point_id, name, condition, trigger_interval, last_status, last_status_at, custom_message) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
`
|
||||||
alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.LastStatus, alert.LastStatusAt, alert.CustomMessage,
|
INSERT INTO alerts
|
||||||
|
(contact_point_id, name, condition, trigger_interval, last_status, last_status_at, custom_message) VALUES
|
||||||
|
(:contact_point_id, :name, :condition, :trigger_interval, :last_status, :last_status_at, :custom_message)
|
||||||
|
`,
|
||||||
|
alert,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -248,6 +77,37 @@ func (s *AlertsService) Create(contactPointId int64, name string, condition stri
|
||||||
return &alert, nil
|
return &alert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64, customMessage string) (*models.AlertItem, error) {
|
||||||
|
alert := models.AlertItem{
|
||||||
|
Id: id,
|
||||||
|
ContactPointId: contactPointId,
|
||||||
|
Name: name,
|
||||||
|
Condition: condition,
|
||||||
|
TriggerInterval: triggerInterval,
|
||||||
|
CustomMessage: customMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := s.ctx.DB.NamedExec(
|
||||||
|
`
|
||||||
|
UPDATE alerts
|
||||||
|
SET
|
||||||
|
name = :name,
|
||||||
|
contact_point_id = :contact_point_id,
|
||||||
|
condition = :condition,
|
||||||
|
trigger_interval = :trigger_interval,
|
||||||
|
custom_message = :custom_message
|
||||||
|
WHERE id = :id
|
||||||
|
`,
|
||||||
|
alert,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &alert, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *AlertsService) DeleteById(id int64) error {
|
func (s *AlertsService) DeleteById(id int64) error {
|
||||||
_, err := s.ctx.DB.Exec("DELETE FROM alerts WHERE id = ?", id)
|
_, err := s.ctx.DB.Exec("DELETE FROM alerts WHERE id = ?", id)
|
||||||
|
|
||||||
|
|
@ -257,25 +117,3 @@ func (s *AlertsService) DeleteById(id int64) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64, customMessage string) (*models.AlertItem, error) {
|
|
||||||
alert := models.AlertItem{
|
|
||||||
Id: id,
|
|
||||||
ContactPointId: contactPointId,
|
|
||||||
Name: name,
|
|
||||||
Condition: condition,
|
|
||||||
TriggerInterval: triggerInterval,
|
|
||||||
CustomMessage: customMessage,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := s.ctx.DB.Exec(
|
|
||||||
"UPDATE alerts SET contact_point_id = ?, name = ?, condition = ?, trigger_interval = ?, custom_message = ? WHERE id = ?",
|
|
||||||
alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.CustomMessage, alert.Id,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &alert, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,20 @@ package services
|
||||||
import (
|
import (
|
||||||
"basic-sensor-receiver/config"
|
"basic-sensor-receiver/config"
|
||||||
"basic-sensor-receiver/integrations"
|
"basic-sensor-receiver/integrations"
|
||||||
"database/sql"
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Services struct {
|
type Services struct {
|
||||||
SensorConfig *SensorConfigService
|
SensorConfig *SensorConfigService
|
||||||
SensorValues *SensorValuesService
|
SensorValues *SensorValuesService
|
||||||
Sensors *SensorsService
|
Sensors *SensorsService
|
||||||
Sessions *SessionsService
|
Sessions *SessionsService
|
||||||
Auth *AuthService
|
Auth *AuthService
|
||||||
Dashboards *DashboardsService
|
Dashboards *DashboardsService
|
||||||
Alerts *AlertsService
|
Alerts *AlertsService
|
||||||
ContactPoints *ContactPointsService
|
AlertsEvaluator *AlertsEvaluatorService
|
||||||
|
ContactPoints *ContactPointsService
|
||||||
}
|
}
|
||||||
|
|
||||||
type Integrations struct {
|
type Integrations struct {
|
||||||
|
|
@ -22,7 +24,7 @@ type Integrations struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
DB *sql.DB
|
DB *sqlx.DB
|
||||||
Config *config.Config
|
Config *config.Config
|
||||||
Services *Services
|
Services *Services
|
||||||
Integrations *Integrations
|
Integrations *Integrations
|
||||||
|
|
@ -40,6 +42,7 @@ func InitializeServices(ctx *Context) *Services {
|
||||||
services.Auth = &AuthService{ctx: ctx}
|
services.Auth = &AuthService{ctx: ctx}
|
||||||
services.Dashboards = &DashboardsService{ctx: ctx}
|
services.Dashboards = &DashboardsService{ctx: ctx}
|
||||||
services.Alerts = &AlertsService{ctx: ctx}
|
services.Alerts = &AlertsService{ctx: ctx}
|
||||||
|
services.AlertsEvaluator = &AlertsEvaluatorService{ctx: ctx}
|
||||||
services.ContactPoints = &ContactPointsService{ctx: ctx}
|
services.ContactPoints = &ContactPointsService{ctx: ctx}
|
||||||
|
|
||||||
ctx.Integrations = &Integrations{}
|
ctx.Integrations = &Integrations{}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue