Data retention

This commit is contained in:
Jan Zípek 2024-04-01 10:33:20 +02:00
parent 2421536497
commit 74a4019dc5
Signed by: kamen
GPG Key ID: A17882625B33AC31
9 changed files with 93 additions and 34 deletions

View File

@ -56,6 +56,7 @@ ENV AUTH_USERNAME admin
ENV AUTH_PASSWORD password
ENV AUTH_KEY password
ENV AUTH_ENABLED true
ENV DATA_RETENTION_IN_DAYS 0
EXPOSE ${PORT}
VOLUME [ "/data" ]

View File

@ -47,13 +47,19 @@ export const DashboardContextProvider = ({
}) => {
const viewport = useViewportSize()
const [dashboardId, setDashboardId] = useState(-1)
const { getValues: getQuery, setValues: setQuery } = useQueryString()
const [dashboardId, setDashboardId] = useState(() => {
const query = getQuery()
const queryDashboardId = +(query['dashboard'] ?? '')
return !isNaN(queryDashboardId) ? queryDashboardId : -1
})
const isDashboardSelected = !isNaN(dashboardId) && dashboardId >= 0
const dashboards = useQuery(['/dashboards'], getDashboards)
const { getValues: getQuery, setValues: setQuery } = useQueryString()
const dashboard = useQuery(
['/dashboards', dashboardId],
() => getDashboard(dashboardId),
@ -105,9 +111,9 @@ export const DashboardContextProvider = ({
const [filter, setFilter] = useState<FilterValue>(() => {
const query = getQuery()
const queryFilter = query.get('interval')
const queryFilterFrom = query.get('from')
const queryFilterTo = query.get('to')
const queryFilter = query['interval']
const queryFilterFrom = query['from']
const queryFilterTo = query['to']
const presetInterval =
queryFilter &&
@ -129,13 +135,15 @@ export const DashboardContextProvider = ({
useEffect(() => {
setQuery({
...getQuery(),
dashboard: dashboardId.toString(),
interval: filter.interval,
...(filter.interval === 'custom' && {
from: filter.customFrom.toISOString(),
to: filter.customTo.toISOString(),
}),
})
}, [filter])
}, [filter, dashboardId])
const verticalMode = viewport.width < 800

View File

@ -21,8 +21,13 @@ export const useQueryString = () => {
const getValues = useCallback(() => {
const [, searchParams] = valuesRef.current
const result = {} as Record<string, string>
return searchParams
searchParams.forEach((value, key) => {
result[key] = value
})
return result
}, [])
const setValues = useCallback((values: Record<string, string>) => {

View File

@ -6,3 +6,5 @@ AUTH_ENABLED=true
AUTH_USERNAME=admin
AUTH_PASSWORD=password
AUTH_KEY=password
# How long should the data be stored
DATA_RETENTION_IN_DAYS=365

View File

@ -1,6 +1,7 @@
package app
import (
"log"
"time"
)
@ -9,7 +10,19 @@ func (s *Server) StartCleaner() {
go func() {
for {
s.Services.Sessions.Cleanup()
err := s.Services.Sessions.Cleanup()
if err != nil {
log.Println("Error cleaning up sessions:", err)
}
if s.Config.DataRetentionInDays > 0 {
err := s.Services.SensorValues.Cleanup(s.Config.DataRetentionInDays)
if err != nil {
log.Println("Error cleaning up sensor values:", err)
}
}
<-ticker.C
}
}()

View File

@ -1,37 +1,51 @@
package config
import (
"fmt"
"os"
"strconv"
)
type Config struct {
Mode string
DatabaseUrl string
Port int
Ip string
AuthEnabled bool
AuthUsername string
AuthPassword string
AuthKey string
Mode string
DatabaseUrl string
Port int
Ip string
AuthEnabled bool
AuthUsername string
AuthPassword string
AuthKey string
DataRetentionInDays int64
}
func LoadConfig() *Config {
port, err := strconv.Atoi(os.Getenv("PORT"))
if err != nil {
panic(err)
panic(fmt.Errorf("PORT must be an integer: %v", err))
}
dataRetentionInDaysStr := os.Getenv("DATA_RETENTION_IN_DAYS")
dataRetentionInDays := 0
if dataRetentionInDaysStr != "" {
dataRetentionInDays, err = strconv.Atoi(dataRetentionInDaysStr)
if err != nil {
panic(fmt.Errorf("DATA_RETENTION_IN_DAYS must be an integer: %v", err))
}
}
config := Config{
Mode: os.Getenv("GIN_MODE"),
DatabaseUrl: os.Getenv("DATABASE_URL"),
Port: port,
Ip: os.Getenv("BIND_IP"),
AuthEnabled: os.Getenv("AUTH_ENABLED") != "false",
AuthUsername: os.Getenv("AUTH_USERNAME"),
AuthPassword: os.Getenv("AUTH_PASSWORD"),
AuthKey: os.Getenv("AUTH_KEY"),
Mode: os.Getenv("GIN_MODE"),
DatabaseUrl: os.Getenv("DATABASE_URL"),
Port: port,
Ip: os.Getenv("BIND_IP"),
AuthEnabled: os.Getenv("AUTH_ENABLED") != "false",
AuthUsername: os.Getenv("AUTH_USERNAME"),
AuthPassword: os.Getenv("AUTH_PASSWORD"),
AuthKey: os.Getenv("AUTH_KEY"),
DataRetentionInDays: int64(dataRetentionInDays),
}
// TODO: Crash when any auth* param is empty

View File

@ -84,10 +84,9 @@ func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
Value: condition["value"].(float64),
}
value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix())
lastValue = value.Value
sensorId = int64(sensorValueCondition.SensorId)
value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix())
if err != nil {
if err == sql.ErrNoRows {
lastValue = float64(0)
@ -96,6 +95,7 @@ func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
return fmt.Errorf("error getting sensor value: %v", err)
}
} else {
lastValue = value.Value
conditionMet = evaluateSensorValueCondition(sensorValueCondition, value)
}
@ -110,22 +110,23 @@ func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
ValueUnit: condition["valueUnit"].(string),
}
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
lastValue = float64(value.Timestamp)
sensorId = sensorLastContactCondition.SensorId
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
if err != nil {
if err == sql.ErrNoRows {
lastValue = float64(0)
} else {
return fmt.Errorf("error getting sensor last contact value: %v", err)
}
} else {
lastValue = float64(value.Timestamp)
conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit))
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
}
conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit))
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
break
}

View File

@ -0,0 +1 @@
package services

View File

@ -2,6 +2,7 @@ package services
import (
"database/sql"
"fmt"
"time"
)
@ -41,6 +42,19 @@ func (s *SensorValuesService) GetLatest(sensorId int64, to int64) (*sensorValue,
return &value, nil
}
func (s *SensorValuesService) Cleanup(retentionInDays int64) error {
if retentionInDays <= 0 {
return fmt.Errorf("retentionInDays must be greater than 0")
}
_, err := s.ctx.DB.Exec("DELETE FROM sensor_values WHERE timestamp < ?", time.Now().Unix()-(retentionInDays*24*60*60))
if err != nil {
return err
}
return nil
}
func (s *SensorValuesService) getValueListQuery(sensorId int64, from int64, to int64, divide int64) (*sql.Rows, error) {
if divide == 1 {
return s.ctx.DB.Query("SELECT timestamp, value FROM sensor_values WHERE sensor_id = ? AND timestamp > ? AND timestamp < ? ORDER BY timestamp ASC", sensorId, from, to)