diff --git a/docs/dbdiagram.io.txt b/docs/dbdiagram.io.txt index 23265ad..ce1dd83 100644 --- a/docs/dbdiagram.io.txt +++ b/docs/dbdiagram.io.txt @@ -2,6 +2,7 @@ // SQLite Table(s) Table Device { + Archived bool //GORM attributes, see: http://gorm.io/docs/conventions.html CreatedAt time UpdatedAt time diff --git a/webapp/backend/pkg/database/interface.go b/webapp/backend/pkg/database/interface.go index 8613eae..ee7066e 100644 --- a/webapp/backend/pkg/database/interface.go +++ b/webapp/backend/pkg/database/interface.go @@ -20,6 +20,7 @@ type DeviceRepo interface { UpdateDevice(ctx context.Context, wwn string, collectorSmartData collector.SmartInfo) (models.Device, error) UpdateDeviceStatus(ctx context.Context, wwn string, status pkg.DeviceStatus) (models.Device, error) GetDeviceDetails(ctx context.Context, wwn string) (models.Device, error) + UpdateDeviceArchived(ctx context.Context, wwn string, archived bool) error DeleteDevice(ctx context.Context, wwn string) error SaveSmartAttributes(ctx context.Context, wwn string, collectorSmartData collector.SmartInfo) (measurements.Smart, error) diff --git a/webapp/backend/pkg/database/migrations/m20220509170100/device.go b/webapp/backend/pkg/database/migrations/m20220509170100/device.go index 1134fff..c8be222 100644 --- a/webapp/backend/pkg/database/migrations/m20220509170100/device.go +++ b/webapp/backend/pkg/database/migrations/m20220509170100/device.go @@ -5,6 +5,7 @@ import ( "time" ) +// Deprecated: m20220509170100.Device is deprecated, only used by db migrations type Device struct { //GORM attributes, see: http://gorm.io/docs/conventions.html CreatedAt time.Time @@ -14,9 +15,9 @@ type Device struct { WWN string `json:"wwn" gorm:"primary_key"` DeviceName string `json:"device_name"` - DeviceUUID string `json:"device_uuid"` - DeviceSerialID string `json:"device_serial_id"` - DeviceLabel string `json:"device_label"` + DeviceUUID string `json:"device_uuid"` + DeviceSerialID string `json:"device_serial_id"` + DeviceLabel string `json:"device_label"` Manufacturer string `json:"manufacturer"` ModelName string `json:"model_name"` @@ -38,4 +39,3 @@ type Device struct { // Data set by Scrutiny DeviceStatus pkg.DeviceStatus `json:"device_status"` } - diff --git a/webapp/backend/pkg/database/migrations/m20250221084400/device.go b/webapp/backend/pkg/database/migrations/m20250221084400/device.go new file mode 100644 index 0000000..b0a2e5a --- /dev/null +++ b/webapp/backend/pkg/database/migrations/m20250221084400/device.go @@ -0,0 +1,41 @@ +package m20250221084400 + +import ( + "github.com/analogj/scrutiny/webapp/backend/pkg" + "time" +) + +type Device struct { + Archived bool `json:"archived"` + //GORM attributes, see: http://gorm.io/docs/conventions.html + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt *time.Time + + WWN string `json:"wwn" gorm:"primary_key"` + + DeviceName string `json:"device_name"` + DeviceUUID string `json:"device_uuid"` + DeviceSerialID string `json:"device_serial_id"` + DeviceLabel string `json:"device_label"` + + Manufacturer string `json:"manufacturer"` + ModelName string `json:"model_name"` + InterfaceType string `json:"interface_type"` + InterfaceSpeed string `json:"interface_speed"` + SerialNumber string `json:"serial_number"` + Firmware string `json:"firmware"` + RotationSpeed int `json:"rotational_speed"` + Capacity int64 `json:"capacity"` + FormFactor string `json:"form_factor"` + SmartSupport bool `json:"smart_support"` + DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI) + DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector. + + // User provided metadata + Label string `json:"label"` + HostId string `json:"host_id"` + + // Data set by Scrutiny + DeviceStatus pkg.DeviceStatus `json:"device_status"` +} diff --git a/webapp/backend/pkg/database/mock/mock_database.go b/webapp/backend/pkg/database/mock/mock_database.go index c001d08..f5fefc2 100644 --- a/webapp/backend/pkg/database/mock/mock_database.go +++ b/webapp/backend/pkg/database/mock/mock_database.go @@ -52,6 +52,20 @@ func (mr *MockDeviceRepoMockRecorder) Close() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockDeviceRepo)(nil).Close)) } +// UpdateDeviceArchived mocks base method. +func (m *MockDeviceRepo) UpdateDeviceArchived(ctx context.Context, wwn string, archived bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDeviceArchived", ctx, wwn) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDeviceArchived indicates an expected call of UpdateDeviceArchived. +func (mr *MockDeviceRepoMockRecorder) UpdateDeviceArchived(ctx, wwn, archived interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDeviceArchived", reflect.TypeOf((*MockDeviceRepo)(nil).UpdateDeviceArchived), ctx, wwn, archived) +} + // DeleteDevice mocks base method. func (m *MockDeviceRepo) DeleteDevice(ctx context.Context, wwn string) error { m.ctrl.T.Helper() diff --git a/webapp/backend/pkg/database/scrutiny_repository_device.go b/webapp/backend/pkg/database/scrutiny_repository_device.go index 897a3e1..8ba0e8c 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_device.go +++ b/webapp/backend/pkg/database/scrutiny_repository_device.go @@ -14,7 +14,7 @@ import ( // Device //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//insert device into DB (and update specified columns if device is already registered) +// insert device into DB (and update specified columns if device is already registered) // update device fields that may change: (DeviceType, HostID) func (sr *scrutinyRepository) RegisterDevice(ctx context.Context, dev models.Device) error { if err := sr.gormClient.WithContext(ctx).Clauses(clause.OnConflict{ @@ -51,7 +51,7 @@ func (sr *scrutinyRepository) UpdateDevice(ctx context.Context, wwn string, coll return device, sr.gormClient.Model(&device).Updates(device).Error } -//Update Device Status +// Update Device Status func (sr *scrutinyRepository) UpdateDeviceStatus(ctx context.Context, wwn string, status pkg.DeviceStatus) (models.Device, error) { var device models.Device if err := sr.gormClient.WithContext(ctx).Where("wwn = ?", wwn).First(&device).Error; err != nil { @@ -74,6 +74,16 @@ func (sr *scrutinyRepository) GetDeviceDetails(ctx context.Context, wwn string) return device, nil } +// Update Device Archived State +func (sr *scrutinyRepository) UpdateDeviceArchived(ctx context.Context, wwn string, archived bool) error { + var device models.Device + if err := sr.gormClient.WithContext(ctx).Where("wwn = ?", wwn).First(&device).Error; err != nil { + return fmt.Errorf("Could not get device from DB: %v", err) + } + + return sr.gormClient.Model(&device).Where("wwn = ?", wwn).Update("archived", archived).Error +} + func (sr *scrutinyRepository) DeleteDevice(ctx context.Context, wwn string) error { if err := sr.gormClient.WithContext(ctx).Where("wwn = ?", wwn).Delete(&models.Device{}).Error; err != nil { return err diff --git a/webapp/backend/pkg/database/scrutiny_repository_migrations.go b/webapp/backend/pkg/database/scrutiny_repository_migrations.go index 9a02574..e12fae8 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_migrations.go +++ b/webapp/backend/pkg/database/scrutiny_repository_migrations.go @@ -12,6 +12,7 @@ import ( "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220503120000" "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220509170100" "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220716214900" + "github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20250221084400" "github.com/analogj/scrutiny/webapp/backend/pkg/models" "github.com/analogj/scrutiny/webapp/backend/pkg/models/collector" "github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements" @@ -399,6 +400,15 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { return tx.Create(&defaultSettings).Error }, }, + { + ID: "m20250221084400", // add archived to device data + Migrate: func(tx *gorm.DB) error { + + //migrate the device database. + // adding column (archived) + return tx.AutoMigrate(m20250221084400.Device{}) + }, + }, }) if err := m.Migrate(); err != nil { diff --git a/webapp/backend/pkg/models/device.go b/webapp/backend/pkg/models/device.go index 9c84115..a891652 100644 --- a/webapp/backend/pkg/models/device.go +++ b/webapp/backend/pkg/models/device.go @@ -14,6 +14,7 @@ type DeviceWrapper struct { type Device struct { //GORM attributes, see: http://gorm.io/docs/conventions.html + Archived bool `json:"archived"` CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time diff --git a/webapp/backend/pkg/web/handler/archive_device.go b/webapp/backend/pkg/web/handler/archive_device.go new file mode 100644 index 0000000..494dd38 --- /dev/null +++ b/webapp/backend/pkg/web/handler/archive_device.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/analogj/scrutiny/webapp/backend/pkg/database" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "net/http" +) + +func ArchiveDevice(c *gin.Context) { + logger := c.MustGet("LOGGER").(*logrus.Entry) + deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo) + + err := deviceRepo.UpdateDeviceArchived(c, c.Param("wwn"), true) + if err != nil { + logger.Errorln("An error occurred while archiving device", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + + c.JSON(http.StatusOK, gin.H{"success": true}) +} diff --git a/webapp/backend/pkg/web/handler/unarchive_device.go b/webapp/backend/pkg/web/handler/unarchive_device.go new file mode 100644 index 0000000..dea8781 --- /dev/null +++ b/webapp/backend/pkg/web/handler/unarchive_device.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/analogj/scrutiny/webapp/backend/pkg/database" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" + "net/http" +) + +func UnarchiveDevice(c *gin.Context) { + logger := c.MustGet("LOGGER").(*logrus.Entry) + deviceRepo := c.MustGet("DEVICE_REPOSITORY").(database.DeviceRepo) + + err := deviceRepo.UpdateDeviceArchived(c, c.Param("wwn"), false) + if err != nil { + logger.Errorln("An error occurred while unarchiving device", err) + c.JSON(http.StatusInternalServerError, gin.H{"success": false}) + return + } + + c.JSON(http.StatusOK, gin.H{"success": true}) +} diff --git a/webapp/backend/pkg/web/server.go b/webapp/backend/pkg/web/server.go index 3d56fe0..38383c9 100644 --- a/webapp/backend/pkg/web/server.go +++ b/webapp/backend/pkg/web/server.go @@ -42,8 +42,10 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine { api.GET("/summary/temp", handler.GetDevicesSummaryTempHistory) //used by Dashboard (Temperature history dropdown) api.POST("/device/:wwn/smart", handler.UploadDeviceMetrics) //used by Collector to upload data api.POST("/device/:wwn/selftest", handler.UploadDeviceSelfTests) - api.GET("/device/:wwn/details", handler.GetDeviceDetails) //used by Details - api.DELETE("/device/:wwn", handler.DeleteDevice) //used by UI to delete device + api.GET("/device/:wwn/details", handler.GetDeviceDetails) //used by Details + api.POST("/device/:wwn/archive", handler.ArchiveDevice) //used by UI to archive device + api.POST("/device/:wwn/unarchive", handler.UnarchiveDevice) //used by UI to unarchive device + api.DELETE("/device/:wwn", handler.DeleteDevice) //used by UI to delete device api.GET("/settings", handler.GetSettings) //used to get settings api.POST("/settings", handler.SaveSettings) //used to save settings diff --git a/webapp/frontend/src/app/core/models/device-model.ts b/webapp/frontend/src/app/core/models/device-model.ts index 7613c3f..bddb776 100644 --- a/webapp/frontend/src/app/core/models/device-model.ts +++ b/webapp/frontend/src/app/core/models/device-model.ts @@ -1,5 +1,6 @@ // maps to webapp/backend/pkg/models/device.go export interface DeviceModel { + archived?: boolean; wwn: string; device_name?: string; device_uuid?: string; diff --git a/webapp/frontend/src/app/data/mock/device/details/sda.ts b/webapp/frontend/src/app/data/mock/device/details/sda.ts index 6406e5a..b0b0d4d 100644 --- a/webapp/frontend/src/app/data/mock/device/details/sda.ts +++ b/webapp/frontend/src/app/data/mock/device/details/sda.ts @@ -20,7 +20,8 @@ export const sda = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false }, 'smart_results': [{ 'date': '2021-10-24T23:20:44Z', diff --git a/webapp/frontend/src/app/data/mock/summary/data.ts b/webapp/frontend/src/app/data/mock/summary/data.ts index 8530a8d..5156f9e 100644 --- a/webapp/frontend/src/app/data/mock/summary/data.ts +++ b/webapp/frontend/src/app/data/mock/summary/data.ts @@ -28,7 +28,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false } }, '0x5000cca252c859cc': { @@ -55,7 +56,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false }, 'smart': { 'collector_date': '2020-08-21T22:27:02Z', @@ -91,7 +93,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false }, 'smart': { 'collector_date': '2020-06-21T00:03:30Z', @@ -127,7 +130,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false } }, '0x5000cca264ec3183': { @@ -154,7 +158,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': 'custom host id', - 'device_status': 1 + 'device_status': 1, + 'archived': false }, 'smart': { 'collector_date': '2020-09-13T16:29:23Z', @@ -574,7 +579,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false } }, '0x5002538e40a22954': { @@ -601,7 +607,8 @@ export const summary = { 'device_type': '', 'label': '', 'host_id': '', - 'device_status': 0 + 'device_status': 0, + 'archived': false }, 'smart': { 'collector_date': '2020-06-10T12:01:02Z', diff --git a/webapp/frontend/src/app/layout/common/dashboard-device-archive-dialog/dashboard-device-archive-dialog.component.html b/webapp/frontend/src/app/layout/common/dashboard-device-archive-dialog/dashboard-device-archive-dialog.component.html new file mode 100644 index 0000000..aa8d88e --- /dev/null +++ b/webapp/frontend/src/app/layout/common/dashboard-device-archive-dialog/dashboard-device-archive-dialog.component.html @@ -0,0 +1,11 @@ +