diff --git a/webapp/backend/pkg/config/config.go b/webapp/backend/pkg/config/config.go index 3d689f3..3961373 100644 --- a/webapp/backend/pkg/config/config.go +++ b/webapp/backend/pkg/config/config.go @@ -9,7 +9,7 @@ import ( "strings" ) -const DBSETTING_SUBKEY = "dbsetting" +const DB_USER_SETTINGS_SUBKEY = "user" // When initializing this class the following methods must be called: // Config.New diff --git a/webapp/backend/pkg/database/migrations/m20220716214900/setting.go b/webapp/backend/pkg/database/migrations/m20220716214900/setting.go index 8e38845..9c1f746 100644 --- a/webapp/backend/pkg/database/migrations/m20220716214900/setting.go +++ b/webapp/backend/pkg/database/migrations/m20220716214900/setting.go @@ -12,6 +12,6 @@ type Setting struct { SettingKeyDescription string `json:"setting_key_description"` SettingDataType string `json:"setting_data_type"` - SettingValueNumeric int64 `json:"setting_value_numeric"` + SettingValueNumeric int `json:"setting_value_numeric"` SettingValueString string `json:"setting_value_string"` } diff --git a/webapp/backend/pkg/database/scrutiny_repository_migrations.go b/webapp/backend/pkg/database/scrutiny_repository_migrations.go index 26f01fd..3be1071 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_migrations.go +++ b/webapp/backend/pkg/database/scrutiny_repository_migrations.go @@ -290,22 +290,53 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { var defaultSettings = []m20220716214900.Setting{ { - SettingKeyName: "metrics.notify.level", + SettingKeyName: "theme", + SettingKeyDescription: "Frontend theme ('light' | 'dark' | 'system')", + SettingDataType: "string", + SettingValueString: "system", // options: 'light' | 'dark' | 'system' + }, + { + SettingKeyName: "layout", + SettingKeyDescription: "Frontend layout ('material')", + SettingDataType: "string", + SettingValueString: "material", + }, + { + SettingKeyName: "dashboardDisplay", + SettingKeyDescription: "Frontend device display title ('name' | 'serial_id' | 'uuid' | 'label')", + SettingDataType: "string", + SettingValueString: "name", + }, + { + SettingKeyName: "dashboardSort", + SettingKeyDescription: "Frontend device sort by ('status' | 'title' | 'age')", + SettingDataType: "string", + SettingValueString: "status", + }, + { + SettingKeyName: "temperatureUnit", + SettingKeyDescription: "Frontend temperature unit ('celsius' | 'fahrenheit')", + SettingDataType: "string", + SettingValueString: "celsius", + }, + + { + SettingKeyName: "metrics.notifyLevel", SettingKeyDescription: "Determines which device status will cause a notification (fail or warn)", SettingDataType: "numeric", - SettingValueNumeric: int64(pkg.MetricsNotifyLevelFail), // options: 'fail' or 'warn' + SettingValueNumeric: int(pkg.MetricsNotifyLevelFail), // options: 'fail' or 'warn' }, { - SettingKeyName: "metrics.status.filter_attributes", + SettingKeyName: "metrics.statusFilterAttributes", SettingKeyDescription: "Determines which attributes should impact device status", SettingDataType: "numeric", - SettingValueNumeric: int64(pkg.MetricsStatusFilterAttributesAll), // options: 'all' or 'critical' + SettingValueNumeric: int(pkg.MetricsStatusFilterAttributesAll), // options: 'all' or 'critical' }, { - SettingKeyName: "metrics.status.threshold", + SettingKeyName: "metrics.statusThreshold", SettingKeyDescription: "Determines which threshold should impact device status", SettingDataType: "numeric", - SettingValueNumeric: int64(pkg.MetricsStatusThresholdBoth), // options: 'scrutiny', 'smart', 'both' + SettingValueNumeric: int(pkg.MetricsStatusThresholdBoth), // options: 'scrutiny', 'smart', 'both' }, } return tx.Create(&defaultSettings).Error diff --git a/webapp/backend/pkg/database/scrutiny_repository_settings.go b/webapp/backend/pkg/database/scrutiny_repository_settings.go index 329b375..7e874b3 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_settings.go +++ b/webapp/backend/pkg/database/scrutiny_repository_settings.go @@ -17,7 +17,7 @@ func (sr *scrutinyRepository) LoadSettings(ctx context.Context) (*models.Setting // store retrieved settings in the AppConfig obj for _, settingsEntry := range settingsEntries { - configKey := fmt.Sprintf("%s.%s", config.DBSETTING_SUBKEY, settingsEntry.SettingKeyName) + configKey := fmt.Sprintf("%s.%s", config.DB_USER_SETTINGS_SUBKEY, settingsEntry.SettingKeyName) if settingsEntry.SettingDataType == "numeric" { sr.appConfig.Set(configKey, settingsEntry.SettingValueNumeric) @@ -28,7 +28,7 @@ func (sr *scrutinyRepository) LoadSettings(ctx context.Context) (*models.Setting // unmarshal the dbsetting object data to a settings object. var settings models.Settings - err := sr.appConfig.UnmarshalKey(config.DBSETTING_SUBKEY, &settings) + err := sr.appConfig.UnmarshalKey(config.DB_USER_SETTINGS_SUBKEY, &settings) if err != nil { return nil, err } @@ -36,7 +36,7 @@ func (sr *scrutinyRepository) LoadSettings(ctx context.Context) (*models.Setting } // testing -// curl -d '{"metrics": { "notify": { "level": 5 }, "status": { "filter_attributes": 5, "threshold": 5 } }}' -H "Content-Type: application/json" -X POST http://localhost:9090/api/settings +// curl -d '{"metrics": { "notifyLevel": 5, "statusFilterAttributes": 5, "statusThreshold": 5 }}' -H "Content-Type: application/json" -X POST http://localhost:9090/api/settings // SaveSettings will update settings in AppConfig object, then save the settings to the database. func (sr *scrutinyRepository) SaveSettings(ctx context.Context, settings models.Settings) error { @@ -47,7 +47,7 @@ func (sr *scrutinyRepository) SaveSettings(ctx context.Context, settings models. return err } settingsWrapperMap := map[string]interface{}{} - settingsWrapperMap[config.DBSETTING_SUBKEY] = *settingsMap + settingsWrapperMap[config.DB_USER_SETTINGS_SUBKEY] = *settingsMap err = sr.appConfig.MergeConfigMap(settingsWrapperMap) if err != nil { return err @@ -61,7 +61,7 @@ func (sr *scrutinyRepository) SaveSettings(ctx context.Context, settings models. //update settingsEntries for ndx, settingsEntry := range settingsEntries { - configKey := fmt.Sprintf("%s.%s", config.DBSETTING_SUBKEY, settingsEntry.SettingKeyName) + configKey := fmt.Sprintf("%s.%s", config.DB_USER_SETTINGS_SUBKEY, settingsEntry.SettingKeyName) if settingsEntry.SettingDataType == "numeric" { settingsEntries[ndx].SettingValueNumeric = sr.appConfig.GetInt(configKey) diff --git a/webapp/backend/pkg/models/settings.go b/webapp/backend/pkg/models/settings.go index ec29681..49860f4 100644 --- a/webapp/backend/pkg/models/settings.go +++ b/webapp/backend/pkg/models/settings.go @@ -8,13 +8,15 @@ package models //} type Settings struct { + Theme string `json:"theme" mapstructure:"theme"` + Layout string `json:"layout" mapstructure:"layout"` + DashboardDisplay string `json:"dashboardDisplay" mapstructure:"dashboardDisplay"` + DashboardSort string `json:"dashboardSort" mapstructure:"dashboardSort"` + TemperatureUnit string `json:"temperatureUnit" mapstructure:"temperatureUnit"` + Metrics struct { - Notify struct { - Level int `json:"level" mapstructure:"level"` - } `json:"notify" mapstructure:"notify"` - Status struct { - FilterAttributes int `json:"filter_attributes" mapstructure:"filter_attributes"` - Threshold int `json:"threshold" mapstructure:"threshold"` - } `json:"status" mapstructure:"status"` + NotifyLevel int `json:"notifyLevel" mapstructure:"notifyLevel"` + StatusFilterAttributes int `json:"statusFilterAttributes" mapstructure:"statusFilterAttributes"` + StatusThreshold int `json:"statusThreshold" mapstructure:"statusThreshold"` } `json:"metrics" mapstructure:"metrics"` } diff --git a/webapp/backend/pkg/web/handler/upload_device_metrics.go b/webapp/backend/pkg/web/handler/upload_device_metrics.go index 0d4b74c..a80e4ed 100644 --- a/webapp/backend/pkg/web/handler/upload_device_metrics.go +++ b/webapp/backend/pkg/web/handler/upload_device_metrics.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "github.com/analogj/scrutiny/webapp/backend/pkg" "github.com/analogj/scrutiny/webapp/backend/pkg/config" "github.com/analogj/scrutiny/webapp/backend/pkg/database" @@ -70,8 +71,8 @@ func UploadDeviceMetrics(c *gin.Context) { if notify.ShouldNotify( updatedDevice, smartData, - pkg.MetricsStatusThreshold(appConfig.GetInt("dbsetting.metrics.status.threshold")), - pkg.MetricsStatusFilterAttributes(appConfig.GetInt("dbsetting.metrics.status.filter_attributes")), + pkg.MetricsStatusThreshold(appConfig.GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY))), + pkg.MetricsStatusFilterAttributes(appConfig.GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY))), ) { //send notifications diff --git a/webapp/backend/pkg/web/server_test.go b/webapp/backend/pkg/web/server_test.go index 28b78cc..f2c2d07 100644 --- a/webapp/backend/pkg/web/server_test.go +++ b/webapp/backend/pkg/web/server_test.go @@ -3,7 +3,9 @@ package web_test import ( "bytes" "encoding/json" + "fmt" "github.com/analogj/scrutiny/webapp/backend/pkg" + "github.com/analogj/scrutiny/webapp/backend/pkg/config" mock_config "github.com/analogj/scrutiny/webapp/backend/pkg/config/mock" "github.com/analogj/scrutiny/webapp/backend/pkg/models" "github.com/analogj/scrutiny/webapp/backend/pkg/models/collector" @@ -192,9 +194,9 @@ func (suite *ServerTestSuite) TestUploadDeviceMetricsRoute() { } else { fakeConfig.EXPECT().GetString("web.influxdb.host").Return("localhost").AnyTimes() } - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) ae := web.AppEngine{ Config: fakeConfig, @@ -230,9 +232,9 @@ func (suite *ServerTestSuite) TestPopulateMultiple() { fakeConfig.EXPECT().UnmarshalKey(gomock.Any(), gomock.Any()).AnyTimes().Return(nil) //fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return("testdata/scrutiny_test.db") fakeConfig.EXPECT().GetStringSlice("notify.urls").Return([]string{}).AnyTimes() - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return(path.Join(parentPath, "scrutiny_test.db")) fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return(parentPath) fakeConfig.EXPECT().GetString("web.listen.basepath").Return(suite.Basepath).AnyTimes() @@ -342,9 +344,9 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_WebhookFailure() { fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"https://unroutable.domain.example.asdfghj"}) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { // when running test suite in github actions, we run an influxdb service as a sidecar. @@ -387,9 +389,9 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ScriptFailure() { fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"script:///missing/path/on/disk"}) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { // when running test suite in github actions, we run an influxdb service as a sidecar. @@ -432,9 +434,9 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ScriptSuccess() { fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"script:///usr/bin/env"}) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { // when running test suite in github actions, we run an influxdb service as a sidecar. @@ -477,9 +479,9 @@ func (suite *ServerTestSuite) TestSendTestNotificationRoute_ShoutrrrFailure() { fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"discord://invalidtoken@channel"}) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { // when running test suite in github actions, we run an influxdb service as a sidecar. @@ -521,9 +523,9 @@ func (suite *ServerTestSuite) TestGetDevicesSummaryRoute_Nvme() { fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes() fakeConfig.EXPECT().GetBool("web.influxdb.retention_policy").Return(false).AnyTimes() fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{}) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.notify.level").AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.filter_attributes").AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) - fakeConfig.EXPECT().GetInt("dbsetting.metrics.status.threshold").AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.notifyLevel", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsNotifyLevelFail)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusFilterAttributes", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusFilterAttributesAll)) + fakeConfig.EXPECT().GetInt(fmt.Sprintf("%s.metrics.statusThreshold", config.DB_USER_SETTINGS_SUBKEY)).AnyTimes().Return(int(pkg.MetricsStatusThresholdBoth)) if _, isGithubActions := os.LookupEnv("GITHUB_ACTIONS"); isGithubActions { // when running test suite in github actions, we run an influxdb service as a sidecar.