Data retention
This commit is contained in:
parent
2421536497
commit
74a4019dc5
|
|
@ -56,6 +56,7 @@ ENV AUTH_USERNAME admin
|
||||||
ENV AUTH_PASSWORD password
|
ENV AUTH_PASSWORD password
|
||||||
ENV AUTH_KEY password
|
ENV AUTH_KEY password
|
||||||
ENV AUTH_ENABLED true
|
ENV AUTH_ENABLED true
|
||||||
|
ENV DATA_RETENTION_IN_DAYS 0
|
||||||
|
|
||||||
EXPOSE ${PORT}
|
EXPOSE ${PORT}
|
||||||
VOLUME [ "/data" ]
|
VOLUME [ "/data" ]
|
||||||
|
|
|
||||||
|
|
@ -47,13 +47,19 @@ export const DashboardContextProvider = ({
|
||||||
}) => {
|
}) => {
|
||||||
const viewport = useViewportSize()
|
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 isDashboardSelected = !isNaN(dashboardId) && dashboardId >= 0
|
||||||
|
|
||||||
const dashboards = useQuery(['/dashboards'], getDashboards)
|
const dashboards = useQuery(['/dashboards'], getDashboards)
|
||||||
|
|
||||||
const { getValues: getQuery, setValues: setQuery } = useQueryString()
|
|
||||||
|
|
||||||
const dashboard = useQuery(
|
const dashboard = useQuery(
|
||||||
['/dashboards', dashboardId],
|
['/dashboards', dashboardId],
|
||||||
() => getDashboard(dashboardId),
|
() => getDashboard(dashboardId),
|
||||||
|
|
@ -105,9 +111,9 @@ export const DashboardContextProvider = ({
|
||||||
|
|
||||||
const [filter, setFilter] = useState<FilterValue>(() => {
|
const [filter, setFilter] = useState<FilterValue>(() => {
|
||||||
const query = getQuery()
|
const query = getQuery()
|
||||||
const queryFilter = query.get('interval')
|
const queryFilter = query['interval']
|
||||||
const queryFilterFrom = query.get('from')
|
const queryFilterFrom = query['from']
|
||||||
const queryFilterTo = query.get('to')
|
const queryFilterTo = query['to']
|
||||||
|
|
||||||
const presetInterval =
|
const presetInterval =
|
||||||
queryFilter &&
|
queryFilter &&
|
||||||
|
|
@ -129,13 +135,15 @@ export const DashboardContextProvider = ({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setQuery({
|
setQuery({
|
||||||
|
...getQuery(),
|
||||||
|
dashboard: dashboardId.toString(),
|
||||||
interval: filter.interval,
|
interval: filter.interval,
|
||||||
...(filter.interval === 'custom' && {
|
...(filter.interval === 'custom' && {
|
||||||
from: filter.customFrom.toISOString(),
|
from: filter.customFrom.toISOString(),
|
||||||
to: filter.customTo.toISOString(),
|
to: filter.customTo.toISOString(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}, [filter])
|
}, [filter, dashboardId])
|
||||||
|
|
||||||
const verticalMode = viewport.width < 800
|
const verticalMode = viewport.width < 800
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,13 @@ export const useQueryString = () => {
|
||||||
|
|
||||||
const getValues = useCallback(() => {
|
const getValues = useCallback(() => {
|
||||||
const [, searchParams] = valuesRef.current
|
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>) => {
|
const setValues = useCallback((values: Record<string, string>) => {
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,5 @@ AUTH_ENABLED=true
|
||||||
AUTH_USERNAME=admin
|
AUTH_USERNAME=admin
|
||||||
AUTH_PASSWORD=password
|
AUTH_PASSWORD=password
|
||||||
AUTH_KEY=password
|
AUTH_KEY=password
|
||||||
|
# How long should the data be stored
|
||||||
|
DATA_RETENTION_IN_DAYS=365
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -9,7 +10,19 @@ func (s *Server) StartCleaner() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
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
|
<-ticker.C
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
@ -14,13 +15,25 @@ type Config struct {
|
||||||
AuthUsername string
|
AuthUsername string
|
||||||
AuthPassword string
|
AuthPassword string
|
||||||
AuthKey string
|
AuthKey string
|
||||||
|
DataRetentionInDays int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadConfig() *Config {
|
func LoadConfig() *Config {
|
||||||
port, err := strconv.Atoi(os.Getenv("PORT"))
|
port, err := strconv.Atoi(os.Getenv("PORT"))
|
||||||
|
|
||||||
if err != nil {
|
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{
|
config := Config{
|
||||||
|
|
@ -32,6 +45,7 @@ func LoadConfig() *Config {
|
||||||
AuthUsername: os.Getenv("AUTH_USERNAME"),
|
AuthUsername: os.Getenv("AUTH_USERNAME"),
|
||||||
AuthPassword: os.Getenv("AUTH_PASSWORD"),
|
AuthPassword: os.Getenv("AUTH_PASSWORD"),
|
||||||
AuthKey: os.Getenv("AUTH_KEY"),
|
AuthKey: os.Getenv("AUTH_KEY"),
|
||||||
|
DataRetentionInDays: int64(dataRetentionInDays),
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Crash when any auth* param is empty
|
// TODO: Crash when any auth* param is empty
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,9 @@ func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
|
||||||
Value: condition["value"].(float64),
|
Value: condition["value"].(float64),
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix())
|
|
||||||
lastValue = value.Value
|
|
||||||
sensorId = int64(sensorValueCondition.SensorId)
|
sensorId = int64(sensorValueCondition.SensorId)
|
||||||
|
|
||||||
|
value, err := s.ctx.Services.SensorValues.GetLatest(sensorValueCondition.SensorId, time.Now().Unix())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
lastValue = float64(0)
|
lastValue = float64(0)
|
||||||
|
|
@ -96,6 +95,7 @@ func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
|
||||||
return fmt.Errorf("error getting sensor value: %v", err)
|
return fmt.Errorf("error getting sensor value: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
lastValue = value.Value
|
||||||
conditionMet = evaluateSensorValueCondition(sensorValueCondition, value)
|
conditionMet = evaluateSensorValueCondition(sensorValueCondition, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,22 +110,23 @@ func (s *AlertsEvaluatorService) EvaluateAlert(alert *models.AlertItem) error {
|
||||||
ValueUnit: condition["valueUnit"].(string),
|
ValueUnit: condition["valueUnit"].(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
|
|
||||||
lastValue = float64(value.Timestamp)
|
|
||||||
sensorId = sensorLastContactCondition.SensorId
|
sensorId = sensorLastContactCondition.SensorId
|
||||||
|
|
||||||
|
value, err := s.ctx.Services.SensorValues.GetLatest(sensorLastContactCondition.SensorId, time.Now().Unix())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
lastValue = float64(0)
|
lastValue = float64(0)
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("error getting sensor last contact value: %v", err)
|
return fmt.Errorf("error getting sensor last contact value: %v", err)
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
lastValue = float64(value.Timestamp)
|
||||||
conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit))
|
conditionInSec := int64(sensorLastContactCondition.Value * unitToSeconds(sensorLastContactCondition.ValueUnit))
|
||||||
|
|
||||||
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
|
conditionMet = time.Now().Unix()-value.Timestamp > conditionInSec
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
package services
|
||||||
|
|
@ -2,6 +2,7 @@ package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,6 +42,19 @@ func (s *SensorValuesService) GetLatest(sensorId int64, to int64) (*sensorValue,
|
||||||
return &value, nil
|
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) {
|
func (s *SensorValuesService) getValueListQuery(sensorId int64, from int64, to int64, divide int64) (*sql.Rows, error) {
|
||||||
if divide == 1 {
|
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)
|
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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue