diff --git a/webapp/backend/pkg/database/interface.go b/webapp/backend/pkg/database/interface.go index 8613eaec..83bfde50 100644 --- a/webapp/backend/pkg/database/interface.go +++ b/webapp/backend/pkg/database/interface.go @@ -25,7 +25,7 @@ type DeviceRepo interface { SaveSmartAttributes(ctx context.Context, wwn string, collectorSmartData collector.SmartInfo) (measurements.Smart, error) GetSmartAttributeHistory(ctx context.Context, wwn string, durationKey string, selectEntries int, selectEntriesOffset int, attributes []string) ([]measurements.Smart, error) - SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo) error + SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo, retrieveSCTTemperatureHistory bool) error GetSummary(ctx context.Context) (map[string]*models.DeviceSummary, error) GetSmartTemperatureHistory(ctx context.Context, durationKey string) (map[string][]measurements.SmartTemperature, error) diff --git a/webapp/backend/pkg/database/mock/mock_database.go b/webapp/backend/pkg/database/mock/mock_database.go index c001d08a..311bbecb 100644 --- a/webapp/backend/pkg/database/mock/mock_database.go +++ b/webapp/backend/pkg/database/mock/mock_database.go @@ -214,17 +214,17 @@ func (mr *MockDeviceRepoMockRecorder) SaveSmartAttributes(ctx, wwn, collectorSma } // SaveSmartTemperature mocks base method. -func (m *MockDeviceRepo) SaveSmartTemperature(ctx context.Context, wwn, deviceProtocol string, collectorSmartData collector.SmartInfo) error { +func (m *MockDeviceRepo) SaveSmartTemperature(ctx context.Context, wwn, deviceProtocol string, collectorSmartData collector.SmartInfo, retrieveSCTTemperatureHistory bool) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SaveSmartTemperature", ctx, wwn, deviceProtocol, collectorSmartData) + ret := m.ctrl.Call(m, "SaveSmartTemperature", ctx, wwn, deviceProtocol, collectorSmartData, retrieveSCTTemperatureHistory) ret0, _ := ret[0].(error) return ret0 } // SaveSmartTemperature indicates an expected call of SaveSmartTemperature. -func (mr *MockDeviceRepoMockRecorder) SaveSmartTemperature(ctx, wwn, deviceProtocol, collectorSmartData interface{}) *gomock.Call { +func (mr *MockDeviceRepoMockRecorder) SaveSmartTemperature(ctx, wwn, deviceProtocol, collectorSmartData, retrieveSCTTemperatureHistory interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSmartTemperature", reflect.TypeOf((*MockDeviceRepo)(nil).SaveSmartTemperature), ctx, wwn, deviceProtocol, collectorSmartData) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveSmartTemperature", reflect.TypeOf((*MockDeviceRepo)(nil).SaveSmartTemperature), ctx, wwn, deviceProtocol, collectorSmartData, retrieveSCTTemperatureHistory) } // UpdateDevice mocks base method. diff --git a/webapp/backend/pkg/database/scrutiny_repository_migrations.go b/webapp/backend/pkg/database/scrutiny_repository_migrations.go index e1b948b1..ea91c3e8 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_migrations.go +++ b/webapp/backend/pkg/database/scrutiny_repository_migrations.go @@ -385,6 +385,21 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { return tx.Create(&defaultSettings).Error }, }, + { + ID: "m20240719190500", // add retrieve_sct_history setting. + Migrate: func(tx *gorm.DB) error { + //add retrieve_sct_history setting default. + var defaultSettings = []m20220716214900.Setting{ + { + SettingKeyName: "collector.retrieve_sct_temperature_history", + SettingKeyDescription: "Whether to retrieve SCT Temperature history (true | false)", + SettingDataType: "bool", + SettingValueBool: true, + }, + } + return tx.Create(&defaultSettings).Error + }, + }, }) if err := m.Migrate(); err != nil { @@ -421,8 +436,8 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { // helpers -//When adding data to influxdb, an error may be returned if the data point is outside the range of the retention policy. -//This function will ignore retention policy errors, and allow the migration to continue. +// When adding data to influxdb, an error may be returned if the data point is outside the range of the retention policy. +// This function will ignore retention policy errors, and allow the migration to continue. func ignorePastRetentionPolicyError(err error) error { var influxDbWriteError *http.Error if errors.As(err, &influxDbWriteError) { diff --git a/webapp/backend/pkg/database/scrutiny_repository_temperature.go b/webapp/backend/pkg/database/scrutiny_repository_temperature.go index 8d2a8b57..a218a699 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_temperature.go +++ b/webapp/backend/pkg/database/scrutiny_repository_temperature.go @@ -11,24 +11,52 @@ import ( influxdb2 "github.com/influxdata/influxdb-client-go/v2" ) -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Temperature Data -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -func (sr *scrutinyRepository) SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo) error { - // collectorSmartData.AtaSctTemperatureHistory isn't reliable, only use current temperature +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +func (sr *scrutinyRepository) SaveSmartTemperature(ctx context.Context, wwn string, deviceProtocol string, collectorSmartData collector.SmartInfo, retrieveSCTTemperatureHistory bool) error { + if len(collectorSmartData.AtaSctTemperatureHistory.Table) > 0 && retrieveSCTTemperatureHistory { + + for ndx, temp := range collectorSmartData.AtaSctTemperatureHistory.Table { + //temp value may be null, we must skip/ignore them. See #393 + if temp == 0 { + continue + } - smartTemp := measurements.SmartTemperature{ - Date: time.Unix(collectorSmartData.LocalTime.TimeT, 0), - Temp: collectorSmartData.Temperature.Current, - } + minutesOffset := collectorSmartData.AtaSctTemperatureHistory.LoggingIntervalMinutes * int64(ndx) * 60 + smartTemp := measurements.SmartTemperature{ + Date: time.Unix(collectorSmartData.LocalTime.TimeT-minutesOffset, 0), + Temp: temp, + } + + tags, fields := smartTemp.Flatten() + tags["device_wwn"] = wwn + p := influxdb2.NewPoint("temp", + tags, + fields, + smartTemp.Date) + err := sr.influxWriteApi.WritePoint(ctx, p) + if err != nil { + return err + } + } + // also add the current temperature. + } else { - tags, fields := smartTemp.Flatten() - tags["device_wwn"] = wwn - p := influxdb2.NewPoint("temp", - tags, - fields, - smartTemp.Date) - return sr.influxWriteApi.WritePoint(ctx, p) + smartTemp := measurements.SmartTemperature{ + Date: time.Unix(collectorSmartData.LocalTime.TimeT, 0), + Temp: collectorSmartData.Temperature.Current, + } + + tags, fields := smartTemp.Flatten() + tags["device_wwn"] = wwn + p := influxdb2.NewPoint("temp", + tags, + fields, + smartTemp.Date) + return sr.influxWriteApi.WritePoint(ctx, p) + } + return nil } func (sr *scrutinyRepository) GetSmartTemperatureHistory(ctx context.Context, durationKey string) (map[string][]measurements.SmartTemperature, error) { diff --git a/webapp/backend/pkg/models/settings.go b/webapp/backend/pkg/models/settings.go index e5643010..a4b894cc 100644 --- a/webapp/backend/pkg/models/settings.go +++ b/webapp/backend/pkg/models/settings.go @@ -16,6 +16,10 @@ type Settings struct { FileSizeSIUnits bool `json:"file_size_si_units" mapstructure:"file_size_si_units"` LineStroke string `json:"line_stroke" mapstructure:"line_stroke"` + Collector struct { + RetrieveSCTHistory bool `json:"retrieve_sct_temperature_history" mapstructure:"retrieve_sct_temperature_history"` + } `json:"collector" mapstructure:"collector"` + Metrics struct { NotifyLevel int `json:"notify_level" mapstructure:"notify_level"` StatusFilterAttributes int `json:"status_filter_attributes" mapstructure:"status_filter_attributes"` diff --git a/webapp/backend/pkg/web/handler/upload_device_metrics.go b/webapp/backend/pkg/web/handler/upload_device_metrics.go index 08144338..448200c8 100644 --- a/webapp/backend/pkg/web/handler/upload_device_metrics.go +++ b/webapp/backend/pkg/web/handler/upload_device_metrics.go @@ -61,7 +61,7 @@ func UploadDeviceMetrics(c *gin.Context) { } // save smart temperature data (ignore failures) - err = deviceRepo.SaveSmartTemperature(c, c.Param("wwn"), updatedDevice.DeviceProtocol, collectorSmartData) + err = deviceRepo.SaveSmartTemperature(c, c.Param("wwn"), updatedDevice.DeviceProtocol, collectorSmartData, appConfig.GetBool(fmt.Sprintf("%s.collector.retrieve_sct_temperature_history", config.DB_USER_SETTINGS_SUBKEY))) if err != nil { logger.Errorln("An error occurred while saving smartctl temp data", err) c.JSON(http.StatusInternalServerError, gin.H{"success": false}) diff --git a/webapp/backend/pkg/web/server_test.go b/webapp/backend/pkg/web/server_test.go index bd2407c7..802b3965 100644 --- a/webapp/backend/pkg/web/server_test.go +++ b/webapp/backend/pkg/web/server_test.go @@ -191,6 +191,7 @@ func (suite *ServerTestSuite) TestUploadDeviceMetricsRoute() { fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes() fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("user.metrics.repeat_notifications").Return(true).AnyTimes() + fakeConfig.EXPECT().GetBool("user.collector.retrieve_sct_temperature_history").Return(true).AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { @@ -250,6 +251,7 @@ func (suite *ServerTestSuite) TestPopulateMultiple() { fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes() fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("user.metrics.repeat_notifications").Return(true).AnyTimes() + fakeConfig.EXPECT().GetBool("user.collector.retrieve_sct_temperature_history").Return(true).AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { @@ -533,6 +535,7 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() { fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes() fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("user.metrics.repeat_notifications").Return(true).AnyTimes() + fakeConfig.EXPECT().GetBool("user.collector.retrieve_sct_temperature_history").Return(true).AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.tls.insecure_skip_verify").Return(false).AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{}) diff --git a/webapp/frontend/src/app/core/config/app.config.ts b/webapp/frontend/src/app/core/config/app.config.ts index 1b6dfe3e..0065fa9b 100644 --- a/webapp/frontend/src/app/core/config/app.config.ts +++ b/webapp/frontend/src/app/core/config/app.config.ts @@ -50,6 +50,10 @@ export interface AppConfig { line_stroke?: LineStroke; // Settings from Scrutiny API + + collector?: { + retrieve_sct_temperature_history?: boolean + } metrics?: { notify_level?: MetricsNotifyLevel @@ -79,6 +83,10 @@ export const appConfig: AppConfig = { file_size_si_units: false, line_stroke: 'smooth', + + collector: { + retrieve_sct_temperature_history : true, + }, metrics: { notify_level: MetricsNotifyLevel.Fail, diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html index 0eb9a038..f35fa1df 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html @@ -94,6 +94,16 @@