graphicek/server/services/alerts_service.go

303 lines
6.7 KiB
Go

package services
import (
"basic-sensor-receiver/integrations"
"encoding/json"
"fmt"
"time"
)
type AlertsService struct {
ctx *Context
}
type AlertItem struct {
Id int64 `json:"id"`
ContactPointId int64 `json:"contactPointId"`
Name string `json:"name"`
Condition string `json:"condition"`
/* how long does the condition have to be true for the alert to go off */
TriggerInterval int64 `json:"triggerInterval"`
/* current alert status, possible values: good, pending, alerting */
LastStatus string `json:"lastStatus"`
/* time at which was status last changed */
LastStatusAt int64 `json:"lastStatusAt"`
}
type AlertConditionSensorValue struct {
SensorId int64 `json:"sensorId"`
Condition string `json:"condition"`
Value float64 `json:"value"`
}
type AlertConditionSensorLastContact struct {
SensorId int64 `json:"sensorId"`
Value int64 `json:"value"`
ValueUnit string `json:"valueUnit"`
}
/*
Conditions examples:
sensor value (temperature) is less/more/equal to <<number>>
last contact by sensor is more than 5 minutes ago
// When value of sensor 1 is less than 20
{
type: 'sensor_value',
sensorId: 1,
condition: 'less',
value: 20
}
// When last contact by sensor was more than 10 minutes ago
{
type: 'sensor_last_contact',
sensorId: 1,
value: 10
valueUnit: 'minutes'
}
*/
func evaluateSensorValueCondition(condition *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 *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 (s *AlertsService) EvaluateAlert(alert *AlertItem) error {
condition := map[string]interface{}{}
err := json.Unmarshal([]byte(alert.Condition), &condition)
if err != nil {
return err
}
newStatus := alert.LastStatus
lastValue := ""
conditionMet := false
sensorId := int64(-1)
switch condition["type"].(string) {
case "sensor_value":
{
conditionData := AlertConditionSensorValue{
SensorId: condition["sensorId"].(int64),
Condition: condition["condition"].(string),
Value: condition["value"].(float64),
}
value, err := s.ctx.Services.SensorValues.GetLatest(conditionData.SensorId, time.Now().Unix())
lastValue = fmt.Sprintf("%f", value.Value)
sensorId = int64(conditionData.SensorId)
if err != nil {
return err
}
conditionMet = evaluateSensorValueCondition(&conditionData, value)
break
}
/*
TODO:
case "sensor_last_contact":
{
conditionData := AlertConditionSensorLastContact{
SensorId: condition["sensorId"].(int64),
Value: condition["value"].(int64),
ValueUnit: condition["valueUnit"].(string),
}
value, err := s.ctx.Services.Sensors.GetLastContact(conditionData.SensorId)
if err != nil {
return err
}
conditionMet := time.Now().Unix()-value > conditionData.Value
break
}
*/
}
if conditionMet {
if newStatus == "good" {
newStatus = "alerting"
} else if newStatus == "pending" {
if time.Now().Unix()-alert.LastStatusAt > alert.TriggerInterval {
newStatus = "alerting"
}
}
}
if newStatus != alert.LastStatus {
s.ctx.DB.Exec("UPDATE alerts SET last_status = ?, last_status_at = ? WHERE id = ?", newStatus, time.Now().Unix(), alert.Id)
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: "alert",
AlertTriggeredEvent: &integrations.ContactPointAlertTriggeredEvent{
AlertId: alert.Id,
AlertName: alert.Name,
AlertValue: lastValue,
SensorName: sensor.Name,
CustomMessage: "",
},
}, contactPoint.TypeConfig)
if err != nil {
return err
}
}
return nil
}
func (s *AlertsService) GetList() ([]*AlertItem, error) {
rows, err := s.ctx.DB.Query("SELECT 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 := []*AlertItem{}
for rows.Next() {
alert := AlertItem{}
err := rows.Scan(&alert.Id, &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 {
return nil, err
}
return alerts, nil
}
func (s *AlertsService) GetById(id int64) (*AlertItem, error) {
alert := AlertItem{}
row := s.ctx.DB.QueryRow("SELECT id, name, condition, trigger_interval, last_status, last_status_at FROM alerts WHERE id = ?", id)
err := row.Scan(&alert.Id, &alert.Name, &alert.Condition, &alert.TriggerInterval, &alert.LastStatus, &alert.LastStatusAt)
if err != nil {
return nil, err
}
return &alert, nil
}
func (s *AlertsService) Create(contactPointId int64, name string, condition string, triggerInterval int64) (*AlertItem, error) {
alert := AlertItem{
ContactPointId: contactPointId,
Name: name,
Condition: condition,
TriggerInterval: triggerInterval,
LastStatus: "good",
LastStatusAt: 0,
}
res, err := s.ctx.DB.Exec(
"INSERT INTO alerts (contact_point_id, name, condition, trigger_interval, last_status, last_status_at) VALUES (?, ?, ?, ?, ?)",
alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.LastStatus, alert.LastStatusAt,
)
if err != nil {
return nil, err
}
alert.Id, err = res.LastInsertId()
if err != nil {
return nil, err
}
return &alert, nil
}
func (s *AlertsService) DeleteById(id int64) error {
_, err := s.ctx.DB.Exec("DELETE FROM alerts WHERE id = ?", id)
if err != nil {
return err
}
return nil
}
func (s *AlertsService) Update(id int64, contactPointId int64, name string, condition string, triggerInterval int64) (*AlertItem, error) {
alert := AlertItem{
Id: id,
ContactPointId: contactPointId,
Name: name,
Condition: condition,
TriggerInterval: triggerInterval,
}
_, err := s.ctx.DB.Exec(
"UPDATE alerts SET contact_point_id = ?, name = ?, condition = ?, trigger_interval = ? WHERE id = ?",
alert.ContactPointId, alert.Name, alert.Condition, alert.TriggerInterval, alert.Id,
)
if err != nil {
return nil, err
}
return &alert, nil
}