diff --git a/.gitignore b/.gitignore index 0c9d828..51e33aa 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ vendor /scrutiny /scrutiny-collector-metrics-linux-amd64 /scrutiny-web-linux-amd64 +scrutiny-*.db diff --git a/collector/pkg/collector/base.go b/collector/pkg/collector/base.go index cd379e0..d0768a9 100644 --- a/collector/pkg/collector/base.go +++ b/collector/pkg/collector/base.go @@ -135,3 +135,23 @@ func (c *BaseCollector) ExecCmd(cmdName string, cmdArgs []string, workingDir str return stdBuffer.String(), err } + +func (c *BaseCollector) LogSmartctlExitCode(exitCode int) { + if exitCode&0x01 != 0 { + c.logger.Errorln("smartctl could not parse commandline") + } else if exitCode&0x02 != 0 { + c.logger.Errorln("smartctl could not open device") + } else if exitCode&0x04 != 0 { + c.logger.Errorln("smartctl detected a checksum error") + } else if exitCode&0x08 != 0 { + c.logger.Errorln("smartctl detected a failing disk ") + } else if exitCode&0x10 != 0 { + c.logger.Errorln("smartctl detected a disk in pre-fail") + } else if exitCode&0x20 != 0 { + c.logger.Errorln("smartctl detected a disk close to failure") + } else if exitCode&0x40 != 0 { + c.logger.Errorln("smartctl detected a error log with errors") + } else if exitCode&0x80 != 0 { + c.logger.Errorln("smartctl detected a self test log with errors") + } +} diff --git a/collector/pkg/collector/metrics.go b/collector/pkg/collector/metrics.go index 7bd812b..90b13b4 100644 --- a/collector/pkg/collector/metrics.go +++ b/collector/pkg/collector/metrics.go @@ -49,6 +49,7 @@ func (mc *MetricsCollector) Run() error { } mc.logger.Infoln("Sending detected devices to API, for filtering & validation") + mc.logger.Debugf("Detected devices: %v", detectedStorageDevices) err = mc.postJson(apiEndpoint.String(), models.DeviceWrapper{ Data: detectedStorageDevices, }, &deviceRespWrapper) @@ -98,6 +99,7 @@ func (mc *MetricsCollector) Collect(wg *sync.WaitGroup, deviceWWN string, device if exitError, ok := err.(*exec.ExitError); ok { // smartctl command exited with an error, we should still push the data to the API server mc.logger.Errorf("smartctl returned an error code (%d) while processing %s\n", exitError.ExitCode(), deviceName) + mc.LogSmartctlExitCode(exitError.ExitCode()) mc.Publish(deviceWWN, resultBytes) } else { mc.logger.Errorf("error while attempting to execute smartctl: %s\n", deviceName) diff --git a/go.mod b/go.mod index c390435..f61b5a6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14 github.com/fatih/color v1.9.0 github.com/gin-gonic/gin v1.6.3 + github.com/golang/mock v1.4.3 github.com/jaypipes/ghw v0.6.1 github.com/jinzhu/gorm v1.9.14 github.com/kvz/logstreamer v0.0.0-20150507115422-a635b98146f0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d3e1d35 --- /dev/null +++ b/go.sum @@ -0,0 +1,390 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14 h1:wsrSjiqQtseStRIoLLxS4C5IEtXkazZVEPDHq8jW7r8= +github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14/go.mod h1:lJQVqFKMV5/oDGYR2bra2OljcF3CvolAoyDRyOA4k4E= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jaypipes/ghw v0.6.1 h1:Ewt3mdpiyhWotGyzg1ursV/6SnToGcG4215X6rR2af8= +github.com/jaypipes/ghw v0.6.1/go.mod h1:QOXppNRCLGYR1H+hu09FxZPqjNt09bqUZUnOL3Rcero= +github.com/jaypipes/pcidb v0.5.0 h1:4W5gZ+G7QxydevI8/MmmKdnIPJpURqJ2JNXTzfLxF5c= +github.com/jaypipes/pcidb v0.5.0/go.mod h1:L2RGk04sfRhp5wvHO0gfRAMoLY/F3PKv/nwJeVoho0o= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jinzhu/gorm v1.9.14 h1:Kg3ShyTPcM6nzVo148fRrcMO6MNKuqtOUwnzqMgVniM= +github.com/jinzhu/gorm v1.9.14/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kvz/logstreamer v0.0.0-20150507115422-a635b98146f0 h1:3tLzEnUizyN9YLWFTT9loC30lSBvh2y70LTDcZOTs1s= +github.com/kvz/logstreamer v0.0.0-20150507115422-a635b98146f0/go.mod h1:8/LTPeDLaklcUjgSQBHbhBF1ibKAFxzS5o+H7USfMSA= +github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v0.0.0-20181124034731-591f970eefbb h1:jhnBjNi9UFpfpl8YZhA9CrOqpnJdvzuiHsl/dnxl11M= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/webapp/backend/pkg/config/mock/mock_config.go b/webapp/backend/pkg/config/mock/mock_config.go new file mode 100644 index 0000000..c9ee399 --- /dev/null +++ b/webapp/backend/pkg/config/mock/mock_config.go @@ -0,0 +1,203 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: webapp/backend/pkg/config/interface.go + +// Package mock_config is a generated GoMock package. +package mock_config + +import ( + gomock "github.com/golang/mock/gomock" + viper "github.com/spf13/viper" + reflect "reflect" +) + +// MockInterface is a mock of Interface interface +type MockInterface struct { + ctrl *gomock.Controller + recorder *MockInterfaceMockRecorder +} + +// MockInterfaceMockRecorder is the mock recorder for MockInterface +type MockInterfaceMockRecorder struct { + mock *MockInterface +} + +// NewMockInterface creates a new mock instance +func NewMockInterface(ctrl *gomock.Controller) *MockInterface { + mock := &MockInterface{ctrl: ctrl} + mock.recorder = &MockInterfaceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder { + return m.recorder +} + +// Init mocks base method +func (m *MockInterface) Init() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Init") + ret0, _ := ret[0].(error) + return ret0 +} + +// Init indicates an expected call of Init +func (mr *MockInterfaceMockRecorder) Init() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockInterface)(nil).Init)) +} + +// ReadConfig mocks base method +func (m *MockInterface) ReadConfig(configFilePath string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadConfig", configFilePath) + ret0, _ := ret[0].(error) + return ret0 +} + +// ReadConfig indicates an expected call of ReadConfig +func (mr *MockInterfaceMockRecorder) ReadConfig(configFilePath interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadConfig", reflect.TypeOf((*MockInterface)(nil).ReadConfig), configFilePath) +} + +// Set mocks base method +func (m *MockInterface) Set(key string, value interface{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Set", key, value) +} + +// Set indicates an expected call of Set +func (mr *MockInterfaceMockRecorder) Set(key, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Set", reflect.TypeOf((*MockInterface)(nil).Set), key, value) +} + +// SetDefault mocks base method +func (m *MockInterface) SetDefault(key string, value interface{}) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetDefault", key, value) +} + +// SetDefault indicates an expected call of SetDefault +func (mr *MockInterfaceMockRecorder) SetDefault(key, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDefault", reflect.TypeOf((*MockInterface)(nil).SetDefault), key, value) +} + +// AllSettings mocks base method +func (m *MockInterface) AllSettings() map[string]interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AllSettings") + ret0, _ := ret[0].(map[string]interface{}) + return ret0 +} + +// AllSettings indicates an expected call of AllSettings +func (mr *MockInterfaceMockRecorder) AllSettings() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllSettings", reflect.TypeOf((*MockInterface)(nil).AllSettings)) +} + +// IsSet mocks base method +func (m *MockInterface) IsSet(key string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsSet", key) + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsSet indicates an expected call of IsSet +func (mr *MockInterfaceMockRecorder) IsSet(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsSet", reflect.TypeOf((*MockInterface)(nil).IsSet), key) +} + +// Get mocks base method +func (m *MockInterface) Get(key string) interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", key) + ret0, _ := ret[0].(interface{}) + return ret0 +} + +// Get indicates an expected call of Get +func (mr *MockInterfaceMockRecorder) Get(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), key) +} + +// GetBool mocks base method +func (m *MockInterface) GetBool(key string) bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBool", key) + ret0, _ := ret[0].(bool) + return ret0 +} + +// GetBool indicates an expected call of GetBool +func (mr *MockInterfaceMockRecorder) GetBool(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockInterface)(nil).GetBool), key) +} + +// GetInt mocks base method +func (m *MockInterface) GetInt(key string) int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetInt", key) + ret0, _ := ret[0].(int) + return ret0 +} + +// GetInt indicates an expected call of GetInt +func (mr *MockInterfaceMockRecorder) GetInt(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockInterface)(nil).GetInt), key) +} + +// GetString mocks base method +func (m *MockInterface) GetString(key string) string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetString", key) + ret0, _ := ret[0].(string) + return ret0 +} + +// GetString indicates an expected call of GetString +func (mr *MockInterfaceMockRecorder) GetString(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockInterface)(nil).GetString), key) +} + +// GetStringSlice mocks base method +func (m *MockInterface) GetStringSlice(key string) []string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetStringSlice", key) + ret0, _ := ret[0].([]string) + return ret0 +} + +// GetStringSlice indicates an expected call of GetStringSlice +func (mr *MockInterfaceMockRecorder) GetStringSlice(key interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStringSlice", reflect.TypeOf((*MockInterface)(nil).GetStringSlice), key) +} + +// UnmarshalKey mocks base method +func (m *MockInterface) UnmarshalKey(key string, rawVal interface{}, decoderOpts ...viper.DecoderConfigOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{key, rawVal} + for _, a := range decoderOpts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UnmarshalKey", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// UnmarshalKey indicates an expected call of UnmarshalKey +func (mr *MockInterfaceMockRecorder) UnmarshalKey(key, rawVal interface{}, decoderOpts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{key, rawVal}, decoderOpts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalKey", reflect.TypeOf((*MockInterface)(nil).UnmarshalKey), varargs...) +} diff --git a/webapp/backend/pkg/database/sqlite3.go b/webapp/backend/pkg/database/sqlite3.go index c22dc38..3f4fc45 100644 --- a/webapp/backend/pkg/database/sqlite3.go +++ b/webapp/backend/pkg/database/sqlite3.go @@ -20,7 +20,9 @@ func DatabaseHandler(dbPath string) gin.HandlerFunc { database.AutoMigrate(&db.Device{}) database.AutoMigrate(&db.SelfTest{}) database.AutoMigrate(&db.Smart{}) - database.AutoMigrate(&db.SmartAttribute{}) + database.AutoMigrate(&db.SmartAtaAttribute{}) + database.AutoMigrate(&db.SmartNvmeAttribute{}) + database.AutoMigrate(&db.SmartScsiAttribute{}) //TODO: detrmine where we can call defer database.Close() return func(c *gin.Context) { diff --git a/webapp/backend/pkg/metadata/ata_smart_attributes.go b/webapp/backend/pkg/metadata/ata_attribute_metadata.go similarity index 99% rename from webapp/backend/pkg/metadata/ata_smart_attributes.go rename to webapp/backend/pkg/metadata/ata_attribute_metadata.go index f5174ae..63dc293 100644 --- a/webapp/backend/pkg/metadata/ata_smart_attributes.go +++ b/webapp/backend/pkg/metadata/ata_attribute_metadata.go @@ -4,7 +4,7 @@ const AtaSmartAttributeDisplayTypeRaw = "raw" const AtaSmartAttributeDisplayTypeNormalized = "normalized" const AtaSmartAttributeDisplayTypeTransformed = "transformed" -type AtaSmartAttribute struct { +type AtaAttributeMetadata struct { ID int64 `json:"-"` DisplayName string `json:"-"` Ideal string `json:"ideal"` @@ -28,7 +28,7 @@ type ObservedThreshold struct { ErrorInterval []float64 `json:"error_interval"` } -var AtaSmartAttributes = map[int]AtaSmartAttribute{ +var AtaMetadata = map[int]AtaAttributeMetadata{ 1: { ID: 1, DisplayName: "Read Error Rate", diff --git a/webapp/backend/pkg/metadata/nvme_attribute_metadata.go b/webapp/backend/pkg/metadata/nvme_attribute_metadata.go new file mode 100644 index 0000000..3345205 --- /dev/null +++ b/webapp/backend/pkg/metadata/nvme_attribute_metadata.go @@ -0,0 +1,148 @@ +package metadata + +// https://media.kingston.com/support/downloads/MKP_521.6_SMART-DCP1000_attribute.pdf +// https://www.percona.com/blog/2017/02/09/using-nvme-command-line-tools-to-check-nvme-flash-health/ +// https://nvmexpress.org/resources/nvm-express-technology-features/nvme-features-for-error-reporting-smart-log-pages-failures-and-management-capabilities-in-nvme-architectures/ +// https://www.micromat.com/product_manuals/drive_scope_manual_01.pdf +type NvmeAttributeMetadata struct { + ID string `json:"-"` + DisplayName string `json:"-"` + Ideal string `json:"ideal"` + Critical bool `json:"critical"` + Description string `json:"description"` + + Transform func(int, int64, string) int64 `json:"-"` //this should be a method to extract/tranform the normalized or raw data to a chartable format. Str + TransformValueUnit string `json:"transform_value_unit,omitempty"` + DisplayType string `json:"display_type"` //"raw" "normalized" or "transformed" +} + +var NmveMetadata = map[string]NvmeAttributeMetadata{ + "critical_warning": { + ID: "critical_warning", + DisplayName: "Critical Warning", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "This field indicates critical warnings for the state of the controller. Each bit corresponds to a critical warning type; multiple bits may be set. If a bit is cleared to ‘0’, then that critical warning does not apply. Critical warnings may result in an asynchronous event notification to the host. Bits in this field represent the current associated state and are not persistent.", + }, + "temperature": { + ID: "temperature", + DisplayName: "Temperature", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "available_spare": { + ID: "available_spare", + DisplayName: "Available Spare", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "Contains a normalized percentage (0 to 100%) of the remaining spare capacity available.", + }, + "percentage_used": { + ID: "percentage_used", + DisplayName: "Percentage Used", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "Contains a vendor specific estimate of the percentage of NVM subsystem life used based on the actual usage and the manufacturer’s prediction of NVM life. A value of 100 indicates that the estimated endurance of the NVM in the NVM subsystem has been consumed, but may not indicate an NVM subsystem failure. The value is allowed to exceed 100. Percentages greater than 254 shall be represented as 255. This value shall be updated once per power-on hour (when the controller is not in a sleep state).", + }, + "data_units_read": { + ID: "data_units_read", + DisplayName: "Data Units Read", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of 512 byte data units the host has read from the controller; this value does not include metadata. This value is reported in thousands (i.e., a value of 1 corresponds to 1000 units of 512 bytes read) and is rounded up. When the LBA size is a value other than 512 bytes, the controller shall convert the amount of data read to 512 byte units.", + }, + "data_units_written": { + ID: "data_units_written", + DisplayName: "Data Units Written", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of 512 byte data units the host has written to the controller; this value does not include metadata. This value is reported in thousands (i.e., a value of 1 corresponds to 1000 units of 512 bytes written) and is rounded up. When the LBA size is a value other than 512 bytes, the controller shall convert the amount of data written to 512 byte units.", + }, + "host_reads": { + ID: "host_reads", + DisplayName: "Host Reads", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of read commands completed by the controller", + }, + "host_writes": { + ID: "host_writes", + DisplayName: "Host Writes", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of write commands completed by the controller", + }, + "controller_busy_time": { + ID: "controller_busy_time", + DisplayName: "Controller Busy Time", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the amount of time the controller is busy with I/O commands. The controller is busy when there is a command outstanding to an I/O Queue (specifically, a command was issued via an I/O Submission Queue Tail doorbell write and the corresponding completion queue entry has not been posted yet to the associated I/O Completion Queue). This value is reported in minutes.", + }, + "power_cycles": { + ID: "power_cycles", + DisplayName: "Power Cycles", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of power cycles.", + }, + "power_on_hours": { + ID: "power_on_hours", + DisplayName: "Power on Hours", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of power-on hours. Power on hours is always logging, even when in low power mode.", + }, + "unsafe_shutdowns": { + ID: "unsafe_shutdowns", + DisplayName: "Unsafe Shutdowns", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the number of unsafe shutdowns. This count is incremented when a shutdown notification (CC.SHN) is not received prior to loss of power.", + }, + "media_errors": { + ID: "media_errors", + DisplayName: "Media Errors", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "Contains the number of occurrences where the controller detected an unrecovered data integrity error. Errors such as uncorrectable ECC, CRC checksum failure, or LBA tag mismatch are included in this field.", + }, + "num_err_log_entries": { + ID: "num_err_log_entries", + DisplayName: "Numb Err Log Entries", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "Contains the number of Error Information log entries over the life of the controller.", + }, + "warning_temp_time": { + ID: "warning_temp_time", + DisplayName: "Warning Temp Time", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the amount of time in minutes that the controller is operational and the Composite Temperature is greater than or equal to the Warning Composite Temperature Threshold (WCTEMP) field and less than the Critical Composite Temperature Threshold (CCTEMP) field in the Identify Controller data structure.", + }, + "critical_comp_time": { + ID: "critical_comp_time", + DisplayName: "Critical CompTime", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "Contains the amount of time in minutes that the controller is operational and the Composite Temperature is greater the Critical Composite Temperature Threshold (CCTEMP) field in the Identify Controller data structure.", + }, +} diff --git a/webapp/backend/pkg/metadata/scsi_attribute_metadata.go b/webapp/backend/pkg/metadata/scsi_attribute_metadata.go new file mode 100644 index 0000000..63229e6 --- /dev/null +++ b/webapp/backend/pkg/metadata/scsi_attribute_metadata.go @@ -0,0 +1,120 @@ +package metadata + +type ScsiAttributeMetadata struct { + ID string `json:"-"` + DisplayName string `json:"-"` + Ideal string `json:"ideal"` + Critical bool `json:"critical"` + Description string `json:"description"` + + Transform func(int, int64, string) int64 `json:"-"` //this should be a method to extract/tranform the normalized or raw data to a chartable format. Str + TransformValueUnit string `json:"transform_value_unit,omitempty"` + DisplayType string `json:"display_type"` //"raw" "normalized" or "transformed" +} + +var ScsiMetadata = map[string]ScsiAttributeMetadata{ + "scsi_grown_defect_list": { + ID: "scsi_grown_defect_list", + DisplayName: "Grown Defect List", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "", + }, + "read.errors_corrected_by_eccfast": { + ID: "read.errors_corrected_by_eccfast", + DisplayName: "Read Errors Corrected by ECC Fast", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "read.errors_corrected_by_eccdelayed": { + ID: "read.errors_corrected_by_eccdelayed", + DisplayName: "Read Errors Corrected by ECC Delayed", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "read.errors_corrected_by_rereads_rewrites": { + ID: "read.errors_corrected_by_rereads_rewrites", + DisplayName: "Read Errors Corrected by ReReads/ReWrites", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "", + }, + "read.total_errors_corrected": { + ID: "read.total_errors_corrected", + DisplayName: "Read Total Errors Corrected", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "read.correction_algorithm_invocations": { + ID: "read.correction_algorithm_invocations", + DisplayName: "Read Correction Algorithm Invocations", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "read.total_uncorrected_errors": { + ID: "read.total_uncorrected_errors", + DisplayName: "Read Total Uncorrected Errors", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "", + }, + "write.errors_corrected_by_eccfast": { + ID: "write.errors_corrected_by_eccfast", + DisplayName: "Write Errors Corrected by ECC Fast", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "write.errors_corrected_by_eccdelayed": { + ID: "write.errors_corrected_by_eccdelayed", + DisplayName: "Write Errors Corrected by ECC Delayed", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "write.errors_corrected_by_rereads_rewrites": { + ID: "write.errors_corrected_by_rereads_rewrites", + DisplayName: "Write Errors Corrected by ReReads/ReWrites", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "", + }, + "write.total_errors_corrected": { + ID: "write.total_errors_corrected", + DisplayName: "Write Total Errors Corrected", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "write.correction_algorithm_invocations": { + ID: "write.correction_algorithm_invocations", + DisplayName: "Write Correction Algorithm Invocations", + DisplayType: "", + Ideal: "", + Critical: false, + Description: "", + }, + "write.total_uncorrected_errors": { + ID: "write.total_uncorrected_errors", + DisplayName: "Write Total Uncorrected Errors", + DisplayType: "", + Ideal: "", + Critical: true, + Description: "", + }, +} diff --git a/webapp/backend/pkg/models/collector/smart.go b/webapp/backend/pkg/models/collector/smart.go index 5dd1883..c17b0a4 100644 --- a/webapp/backend/pkg/models/collector/smart.go +++ b/webapp/backend/pkg/models/collector/smart.go @@ -70,6 +70,16 @@ type SmartInfo struct { SmartStatus struct { Passed bool `json:"passed"` } `json:"smart_status"` + + PowerOnTime struct { + Hours int64 `json:"hours"` + } `json:"power_on_time"` + PowerCycleCount int64 `json:"power_cycle_count"` + Temperature struct { + Current int64 `json:"current"` + } `json:"temperature"` + + // ATA Protocol Specific Fields AtaSmartData struct { OfflineDataCollection struct { Status struct { @@ -134,17 +144,35 @@ type SmartInfo struct { } `json:"raw"` } `json:"table"` } `json:"ata_smart_attributes"` - PowerOnTime struct { - Hours int64 `json:"hours"` - } `json:"power_on_time"` - PowerCycleCount int64 `json:"power_cycle_count"` - Temperature struct { - Current int64 `json:"current"` - } `json:"temperature"` AtaSmartErrorLog struct { Summary struct { - Revision int `json:"revision"` - Count int `json:"count"` + Revision int `json:"revision"` + Count int `json:"count"` + LoggedCount int `json:"logged_count"` + Table []struct { + ErrorNumber int `json:"error_number"` + LifetimeHours int `json:"lifetime_hours"` + CompletionRegisters struct { + Error int `json:"error"` + Status int `json:"status"` + Count int `json:"count"` + Lba int `json:"lba"` + Device int `json:"device"` + } `json:"completion_registers"` + ErrorDescription string `json:"error_description"` + PreviousCommands []struct { + Registers struct { + Command int `json:"command"` + Features int `json:"features"` + Count int `json:"count"` + Lba int `json:"lba"` + Device int `json:"device"` + DeviceControl int `json:"device_control"` + } `json:"registers"` + PowerupMilliseconds int `json:"powerup_milliseconds"` + CommandName string `json:"command_name"` + } `json:"previous_commands"` + } `json:"table"` } `json:"summary"` } `json:"ata_smart_error_log"` AtaSmartSelfTestLog struct { @@ -183,4 +211,74 @@ type SmartInfo struct { } `json:"flags"` PowerUpScanResumeMinutes int `json:"power_up_scan_resume_minutes"` } `json:"ata_smart_selective_self_test_log"` + + // NVME Protocol Specific Fields + NvmePciVendor struct { + ID int `json:"id"` + SubsystemID int `json:"subsystem_id"` + } `json:"nvme_pci_vendor"` + NvmeIeeeOuiIdentifier int `json:"nvme_ieee_oui_identifier"` + NvmeControllerID int `json:"nvme_controller_id"` + NvmeNumberOfNamespaces int `json:"nvme_number_of_namespaces"` + NvmeNamespaces []struct { + ID int `json:"id"` + Size struct { + Blocks int `json:"blocks"` + Bytes int64 `json:"bytes"` + } `json:"size"` + Capacity struct { + Blocks int `json:"blocks"` + Bytes int64 `json:"bytes"` + } `json:"capacity"` + Utilization struct { + Blocks int `json:"blocks"` + Bytes int64 `json:"bytes"` + } `json:"utilization"` + FormattedLbaSize int `json:"formatted_lba_size"` + } `json:"nvme_namespaces"` + NvmeSmartHealthInformationLog struct { + CriticalWarning int `json:"critical_warning"` + Temperature int `json:"temperature"` + AvailableSpare int `json:"available_spare"` + AvailableSpareThreshold int `json:"available_spare_threshold"` + PercentageUsed int `json:"percentage_used"` + DataUnitsRead int `json:"data_units_read"` + DataUnitsWritten int `json:"data_units_written"` + HostReads int `json:"host_reads"` + HostWrites int `json:"host_writes"` + ControllerBusyTime int `json:"controller_busy_time"` + PowerCycles int `json:"power_cycles"` + PowerOnHours int `json:"power_on_hours"` + UnsafeShutdowns int `json:"unsafe_shutdowns"` + MediaErrors int `json:"media_errors"` + NumErrLogEntries int `json:"num_err_log_entries"` + WarningTempTime int `json:"warning_temp_time"` + CriticalCompTime int `json:"critical_comp_time"` + } `json:"nvme_smart_health_information_log"` + + // SCSI Protocol Specific Fields + Vendor string `json:"vendor"` + Product string `json:"product"` + ScsiVersion string `json:"scsi_version"` + ScsiGrownDefectList int `json:"scsi_grown_defect_list"` + ScsiErrorCounterLog struct { + Read struct { + ErrorsCorrectedByEccfast int `json:"errors_corrected_by_eccfast"` + ErrorsCorrectedByEccdelayed int `json:"errors_corrected_by_eccdelayed"` + ErrorsCorrectedByRereadsRewrites int `json:"errors_corrected_by_rereads_rewrites"` + TotalErrorsCorrected int `json:"total_errors_corrected"` + CorrectionAlgorithmInvocations int `json:"correction_algorithm_invocations"` + GigabytesProcessed string `json:"gigabytes_processed"` + TotalUncorrectedErrors int `json:"total_uncorrected_errors"` + } `json:"read"` + Write struct { + ErrorsCorrectedByEccfast int `json:"errors_corrected_by_eccfast"` + ErrorsCorrectedByEccdelayed int `json:"errors_corrected_by_eccdelayed"` + ErrorsCorrectedByRereadsRewrites int `json:"errors_corrected_by_rereads_rewrites"` + TotalErrorsCorrected int `json:"total_errors_corrected"` + CorrectionAlgorithmInvocations int `json:"correction_algorithm_invocations"` + GigabytesProcessed string `json:"gigabytes_processed"` + TotalUncorrectedErrors int `json:"total_uncorrected_errors"` + } `json:"write"` + } `json:"scsi_error_counter_log"` } diff --git a/webapp/backend/pkg/models/db/device.go b/webapp/backend/pkg/models/db/device.go index 42f3a69..818b9c1 100644 --- a/webapp/backend/pkg/models/db/device.go +++ b/webapp/backend/pkg/models/db/device.go @@ -13,6 +13,10 @@ type DeviceWrapper struct { Data []Device `json:"data"` } +const DeviceProtocolAta = "ATA" +const DeviceProtocolScsi = "SCSI" +const DeviceProtocolNvme = "NVMe" + type Device struct { //GORM attributes, see: http://gorm.io/docs/conventions.html CreatedAt time.Time @@ -21,28 +25,41 @@ type Device struct { WWN string `json:"wwn" gorm:"primary_key"` - DeviceName string `json:"device_name"` - 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"` - - SmartResults []Smart `gorm:"foreignkey:DeviceWWN" json:"smart_results"` + DeviceName string `json:"device_name"` + 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"` + DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag + SmartResults []Smart `gorm:"foreignkey:DeviceWWN" json:"smart_results"` +} + +func (dv *Device) IsAta() bool { + return dv.DeviceProtocol == DeviceProtocolAta +} + +func (dv *Device) IsScsi() bool { + return dv.DeviceProtocol == DeviceProtocolScsi +} + +func (dv *Device) IsNvme() bool { + return dv.DeviceProtocol == DeviceProtocolNvme } //This method requires a device with an array of SmartResults. //It will remove all SmartResults other than the first (the latest one) -//All removed SmartResults, will be processed, grouping SmartAttribute by attribute_id +//All removed SmartResults, will be processed, grouping SmartAtaAttribute by attribute_id // and adding theme to an array called History. func (dv *Device) SquashHistory() error { if len(dv.SmartResults) <= 1 { - return nil //no history found. ignore + return nil //no ataHistory found. ignore } latestSmartResultSlice := dv.SmartResults[0:1] @@ -51,48 +68,94 @@ func (dv *Device) SquashHistory() error { //re-assign the latest slice to the SmartResults field dv.SmartResults = latestSmartResultSlice - //process the historical slice - history := map[int][]SmartAttribute{} - for _, smartResult := range historicalSmartResultSlice { - for _, smartAttribute := range smartResult.SmartAttributes { - if _, ok := history[smartAttribute.AttributeId]; !ok { - history[smartAttribute.AttributeId] = []SmartAttribute{} + //process the historical slice for ATA data + if len(dv.SmartResults[0].AtaAttributes) > 0 { + ataHistory := map[int][]SmartAtaAttribute{} + for _, smartResult := range historicalSmartResultSlice { + for _, smartAttribute := range smartResult.AtaAttributes { + if _, ok := ataHistory[smartAttribute.AttributeId]; !ok { + ataHistory[smartAttribute.AttributeId] = []SmartAtaAttribute{} + } + ataHistory[smartAttribute.AttributeId] = append(ataHistory[smartAttribute.AttributeId], smartAttribute) + } + } + + //now assign the historical slices to the AtaAttributes in the latest SmartResults + for sandx, smartAttribute := range dv.SmartResults[0].AtaAttributes { + if attributeHistory, ok := ataHistory[smartAttribute.AttributeId]; ok { + dv.SmartResults[0].AtaAttributes[sandx].History = attributeHistory } - history[smartAttribute.AttributeId] = append(history[smartAttribute.AttributeId], smartAttribute) } } - //now assign the historical slices to the SmartAttributes in the latest SmartResults - for sandx, smartAttribute := range dv.SmartResults[0].SmartAttributes { - if attributeHistory, ok := history[smartAttribute.AttributeId]; ok { - dv.SmartResults[0].SmartAttributes[sandx].History = attributeHistory + //process the historical slice for Nvme data + if len(dv.SmartResults[0].NvmeAttributes) > 0 { + nvmeHistory := map[string][]SmartNvmeAttribute{} + for _, smartResult := range historicalSmartResultSlice { + for _, smartAttribute := range smartResult.NvmeAttributes { + if _, ok := nvmeHistory[smartAttribute.AttributeId]; !ok { + nvmeHistory[smartAttribute.AttributeId] = []SmartNvmeAttribute{} + } + nvmeHistory[smartAttribute.AttributeId] = append(nvmeHistory[smartAttribute.AttributeId], smartAttribute) + } + } + + //now assign the historical slices to the AtaAttributes in the latest SmartResults + for sandx, smartAttribute := range dv.SmartResults[0].NvmeAttributes { + if attributeHistory, ok := nvmeHistory[smartAttribute.AttributeId]; ok { + dv.SmartResults[0].NvmeAttributes[sandx].History = attributeHistory + } } } + //process the historical slice for Scsi data + if len(dv.SmartResults[0].ScsiAttributes) > 0 { + scsiHistory := map[string][]SmartScsiAttribute{} + for _, smartResult := range historicalSmartResultSlice { + for _, smartAttribute := range smartResult.ScsiAttributes { + if _, ok := scsiHistory[smartAttribute.AttributeId]; !ok { + scsiHistory[smartAttribute.AttributeId] = []SmartScsiAttribute{} + } + scsiHistory[smartAttribute.AttributeId] = append(scsiHistory[smartAttribute.AttributeId], smartAttribute) + } + } + //now assign the historical slices to the AtaAttributes in the latest SmartResults + for sandx, smartAttribute := range dv.SmartResults[0].ScsiAttributes { + if attributeHistory, ok := scsiHistory[smartAttribute.AttributeId]; ok { + dv.SmartResults[0].ScsiAttributes[sandx].History = attributeHistory + } + } + } return nil } func (dv *Device) ApplyMetadataRules() error { + if !dv.IsAta() { + // Scrutiny Observed thresholds not yet available for NVME or SCSI drives + // since most SMART attributes are not present and BackBlaze data not available + return nil + } + //embed metadata in the latest smart attributes object - if len(dv.SmartResults) > 0 { - for ndx, attr := range dv.SmartResults[0].SmartAttributes { + if len(dv.SmartResults) > 0 && len(dv.SmartResults[0].AtaAttributes) > 0 { + for ndx, attr := range dv.SmartResults[0].AtaAttributes { if strings.ToUpper(attr.WhenFailed) == SmartWhenFailedFailingNow { //this attribute has previously failed - dv.SmartResults[0].SmartAttributes[ndx].Status = SmartAttributeStatusFailed - dv.SmartResults[0].SmartAttributes[ndx].StatusReason = "Attribute is failing manufacturer SMART threshold" + dv.SmartResults[0].AtaAttributes[ndx].Status = SmartAttributeStatusFailed + dv.SmartResults[0].AtaAttributes[ndx].StatusReason = "Attribute is failing manufacturer SMART threshold" } else if strings.ToUpper(attr.WhenFailed) == SmartWhenFailedInThePast { - dv.SmartResults[0].SmartAttributes[ndx].Status = SmartAttributeStatusWarning - dv.SmartResults[0].SmartAttributes[ndx].StatusReason = "Attribute has previously failed manufacturer SMART threshold" + dv.SmartResults[0].AtaAttributes[ndx].Status = SmartAttributeStatusWarning + dv.SmartResults[0].AtaAttributes[ndx].StatusReason = "Attribute has previously failed manufacturer SMART threshold" } - if smartMetadata, ok := metadata.AtaSmartAttributes[attr.AttributeId]; ok { - dv.SmartResults[0].SmartAttributes[ndx].MetadataObservedThresholdStatus(smartMetadata) + if smartMetadata, ok := metadata.AtaMetadata[attr.AttributeId]; ok { + dv.SmartResults[0].AtaAttributes[ndx].MetadataObservedThresholdStatus(smartMetadata) } //check if status is blank, set to "passed" - if len(dv.SmartResults[0].SmartAttributes[ndx].Status) == 0 { - dv.SmartResults[0].SmartAttributes[ndx].Status = SmartAttributeStatusPassed + if len(dv.SmartResults[0].AtaAttributes[ndx].Status) == 0 { + dv.SmartResults[0].AtaAttributes[ndx].Status = SmartAttributeStatusPassed } } } @@ -105,6 +168,11 @@ func (dv *Device) UpdateFromCollectorSmartInfo(info collector.SmartInfo) error { dv.RotationSpeed = info.RotationRate dv.Capacity = info.UserCapacity.Bytes dv.FormFactor = info.FormFactor.Name - //dv.SmartSupport = + dv.DeviceProtocol = info.Device.Protocol + dv.DeviceType = info.Device.Type + if len(info.Vendor) > 0 { + dv.Manufacturer = info.Vendor + } + return nil } diff --git a/webapp/backend/pkg/models/db/smart.go b/webapp/backend/pkg/models/db/smart.go index e3a4b9c..208f156 100644 --- a/webapp/backend/pkg/models/db/smart.go +++ b/webapp/backend/pkg/models/db/smart.go @@ -24,7 +24,9 @@ type Smart struct { PowerOnHours int64 `json:"power_on_hours"` PowerCycleCount int64 `json:"power_cycle_count"` - SmartAttributes []SmartAttribute `json:"smart_attributes" gorm:"foreignkey:SmartId"` + AtaAttributes []SmartAtaAttribute `json:"ata_attributes" gorm:"foreignkey:SmartId"` + NvmeAttributes []SmartNvmeAttribute `json:"nvme_attributes" gorm:"foreignkey:SmartId"` + ScsiAttributes []SmartScsiAttribute `json:"scsi_attributes" gorm:"foreignkey:SmartId"` } func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) error { @@ -36,9 +38,27 @@ func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) er sm.PowerCycleCount = info.PowerCycleCount sm.PowerOnHours = info.PowerOnTime.Hours - sm.SmartAttributes = []SmartAttribute{} + // process ATA/NVME/SCSI protocol data + if info.Device.Protocol == DeviceProtocolAta { + sm.ProcessAtaSmartInfo(info) + } else if info.Device.Protocol == DeviceProtocolNvme { + sm.ProcessNvmeSmartInfo(info) + } else if info.Device.Protocol == DeviceProtocolScsi { + sm.ProcessScsiSmartInfo(info) + } + + if info.SmartStatus.Passed { + sm.SmartStatus = "passed" + } else { + sm.SmartStatus = "failed" + } + return nil +} + +func (sm *Smart) ProcessAtaSmartInfo(info collector.SmartInfo) { + sm.AtaAttributes = []SmartAtaAttribute{} for _, collectorAttr := range info.AtaSmartAttributes.Table { - attrModel := SmartAttribute{ + attrModel := SmartAtaAttribute{ AttributeId: collectorAttr.ID, Name: collectorAttr.Name, Value: collectorAttr.Value, @@ -50,101 +70,51 @@ func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) er } //now that we've parsed the data from the smartctl response, lets match it against our metadata rules and add additional Scrutiny specific data. - if smartMetadata, ok := metadata.AtaSmartAttributes[collectorAttr.ID]; ok { + if smartMetadata, ok := metadata.AtaMetadata[collectorAttr.ID]; ok { attrModel.Name = smartMetadata.DisplayName if smartMetadata.Transform != nil { attrModel.TransformedValue = smartMetadata.Transform(attrModel.Value, attrModel.RawValue, attrModel.RawString) } } - sm.SmartAttributes = append(sm.SmartAttributes, attrModel) - } - - if info.SmartStatus.Passed { - sm.SmartStatus = "passed" - } else { - sm.SmartStatus = "failed" + sm.AtaAttributes = append(sm.AtaAttributes, attrModel) } - return nil } -const SmartAttributeStatusPassed = "passed" -const SmartAttributeStatusFailed = "failed" -const SmartAttributeStatusWarning = "warn" - -type SmartAttribute struct { - gorm.Model - - SmartId int `json:"smart_id"` - Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key - - AttributeId int `json:"attribute_id"` - Name string `json:"name"` - Value int `json:"value"` - Worst int `json:"worst"` - Threshold int `json:"thresh"` - RawValue int64 `json:"raw_value"` - RawString string `json:"raw_string"` - WhenFailed string `json:"when_failed"` - - TransformedValue int64 `json:"transformed_value"` - Status string `gorm:"-" json:"status,omitempty"` - StatusReason string `gorm:"-" json:"status_reason,omitempty"` - FailureRate float64 `gorm:"-" json:"failure_rate,omitempty"` - History []SmartAttribute `gorm:"-" json:"history,omitempty"` -} - -// compare the attribute (raw, normalized, transformed) value to observed thresholds, and update status if necessary -func (sa *SmartAttribute) MetadataObservedThresholdStatus(smartMetadata metadata.AtaSmartAttribute) { - //TODO: multiple rules - // try to predict the failure rates for observed thresholds that have 0 failure rate and error bars. - // - if the attribute is critical - // - the failure rate is over 10 - set to failed - // - the attribute does not match any threshold, set to warn - // - if the attribute is not critical - // - if failure rate is above 20 - set to failed - // - if failure rate is above 10 but below 20 - set to warn - - //update the smart attribute status based on Observed thresholds. - var value int64 - if smartMetadata.DisplayType == metadata.AtaSmartAttributeDisplayTypeNormalized { - value = int64(sa.Value) - } else if smartMetadata.DisplayType == metadata.AtaSmartAttributeDisplayTypeTransformed { - value = sa.TransformedValue - } else { - value = sa.RawValue +func (sm *Smart) ProcessNvmeSmartInfo(info collector.SmartInfo) { + sm.NvmeAttributes = []SmartNvmeAttribute{ + {AttributeId: "critical_warning", Name: "Critical Warning", Value: info.NvmeSmartHealthInformationLog.CriticalWarning}, + {AttributeId: "temperature", Name: "Temperature", Value: info.NvmeSmartHealthInformationLog.Temperature}, + {AttributeId: "available_spare", Name: "Available Spare", Value: info.NvmeSmartHealthInformationLog.AvailableSpare, Threshold: info.NvmeSmartHealthInformationLog.AvailableSpareThreshold}, + {AttributeId: "percentage_used", Name: "Percentage Used", Value: info.NvmeSmartHealthInformationLog.PercentageUsed}, + {AttributeId: "data_units_read", Name: "Data Units Read", Value: info.NvmeSmartHealthInformationLog.DataUnitsRead}, + {AttributeId: "data_units_written", Name: "Data Units Written", Value: info.NvmeSmartHealthInformationLog.DataUnitsWritten}, + {AttributeId: "host_reads", Name: "Host Reads", Value: info.NvmeSmartHealthInformationLog.HostReads}, + {AttributeId: "host_writes", Name: "Host Writes", Value: info.NvmeSmartHealthInformationLog.HostWrites}, + {AttributeId: "controller_busy_time", Name: "Controller Busy Time", Value: info.NvmeSmartHealthInformationLog.ControllerBusyTime}, + {AttributeId: "power_cycles", Name: "Power Cycles", Value: info.NvmeSmartHealthInformationLog.PowerCycles}, + {AttributeId: "power_on_hours", Name: "Power on Hours", Value: info.NvmeSmartHealthInformationLog.PowerOnHours}, + {AttributeId: "unsafe_shutdowns", Name: "Unsafe Shutdowns", Value: info.NvmeSmartHealthInformationLog.UnsafeShutdowns}, + {AttributeId: "media_errors", Name: "Media Errors", Value: info.NvmeSmartHealthInformationLog.MediaErrors}, + {AttributeId: "num_err_log_entries", Name: "Numb Err Log Entries", Value: info.NvmeSmartHealthInformationLog.NumErrLogEntries}, + {AttributeId: "warning_temp_time", Name: "Warning Temp Time", Value: info.NvmeSmartHealthInformationLog.WarningTempTime}, + {AttributeId: "critical_comp_time", Name: "Critical CompTime", Value: info.NvmeSmartHealthInformationLog.CriticalCompTime}, } +} - for _, obsThresh := range smartMetadata.ObservedThresholds { - - //check if "value" is in this bucket - if ((obsThresh.Low == obsThresh.High) && value == obsThresh.Low) || - (obsThresh.Low < value && value <= obsThresh.High) { - sa.FailureRate = obsThresh.AnnualFailureRate - - if smartMetadata.Critical { - if obsThresh.AnnualFailureRate >= 0.10 { - sa.Status = SmartAttributeStatusFailed - sa.StatusReason = "Observed Failure Rate for Critical Attribute is greater than 10%" - } - } else { - if obsThresh.AnnualFailureRate >= 0.20 { - sa.Status = SmartAttributeStatusFailed - sa.StatusReason = "Observed Failure Rate for Attribute is greater than 20%" - } else if obsThresh.AnnualFailureRate >= 0.10 { - sa.Status = SmartAttributeStatusWarning - sa.StatusReason = "Observed Failure Rate for Attribute is greater than 10%" - } - } - - //we've found the correct bucket, we can drop out of this loop - return - } +func (sm *Smart) ProcessScsiSmartInfo(info collector.SmartInfo) { + sm.ScsiAttributes = []SmartScsiAttribute{ + {AttributeId: "scsi_grown_defect_list", Name: "Grown Defect List", Value: info.ScsiGrownDefectList}, + {AttributeId: "read.errors_corrected_by_eccfast", Name: "Read Errors Corrected by ECC Fast", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByEccfast}, + {AttributeId: "read.errors_corrected_by_eccdelayed", Name: "Read Errors Corrected by ECC Delayed", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByEccdelayed}, + {AttributeId: "read.errors_corrected_by_rereads_rewrites", Name: "Read Errors Corrected by ReReads/ReWrites", Value: info.ScsiErrorCounterLog.Read.ErrorsCorrectedByRereadsRewrites}, + {AttributeId: "read.total_errors_corrected", Name: "Read Total Errors Corrected", Value: info.ScsiErrorCounterLog.Read.TotalErrorsCorrected}, + {AttributeId: "read.correction_algorithm_invocations", Name: "Read Correction Algorithm Invocations", Value: info.ScsiErrorCounterLog.Read.CorrectionAlgorithmInvocations}, + {AttributeId: "read.total_uncorrected_errors", Name: "Read Total Uncorrected Errors", Value: info.ScsiErrorCounterLog.Read.TotalUncorrectedErrors}, + {AttributeId: "write.errors_corrected_by_eccfast", Name: "Write Errors Corrected by ECC Fast", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByEccfast}, + {AttributeId: "write.errors_corrected_by_eccdelayed", Name: "Write Errors Corrected by ECC Delayed", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByEccdelayed}, + {AttributeId: "write.errors_corrected_by_rereads_rewrites", Name: "Write Errors Corrected by ReReads/ReWrites", Value: info.ScsiErrorCounterLog.Write.ErrorsCorrectedByRereadsRewrites}, + {AttributeId: "write.total_errors_corrected", Name: "Write Total Errors Corrected", Value: info.ScsiErrorCounterLog.Write.TotalErrorsCorrected}, + {AttributeId: "write.correction_algorithm_invocations", Name: "Write Correction Algorithm Invocations", Value: info.ScsiErrorCounterLog.Write.CorrectionAlgorithmInvocations}, + {AttributeId: "write.total_uncorrected_errors", Name: "Write Total Uncorrected Errors", Value: info.ScsiErrorCounterLog.Write.TotalUncorrectedErrors}, } - // no bucket found - if smartMetadata.Critical { - sa.Status = SmartAttributeStatusWarning - sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute" - } - - return } diff --git a/webapp/backend/pkg/models/db/smart_ata_attribute.go b/webapp/backend/pkg/models/db/smart_ata_attribute.go new file mode 100644 index 0000000..fc36efc --- /dev/null +++ b/webapp/backend/pkg/models/db/smart_ata_attribute.go @@ -0,0 +1,88 @@ +package db + +import ( + "github.com/analogj/scrutiny/webapp/backend/pkg/metadata" + "github.com/jinzhu/gorm" +) + +const SmartAttributeStatusPassed = "passed" +const SmartAttributeStatusFailed = "failed" +const SmartAttributeStatusWarning = "warn" + +type SmartAtaAttribute struct { + gorm.Model + + SmartId int `json:"smart_id"` + Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key + + AttributeId int `json:"attribute_id"` + Name string `json:"name"` + Value int `json:"value"` + Worst int `json:"worst"` + Threshold int `json:"thresh"` + RawValue int64 `json:"raw_value"` + RawString string `json:"raw_string"` + WhenFailed string `json:"when_failed"` + + TransformedValue int64 `json:"transformed_value"` + Status string `gorm:"-" json:"status,omitempty"` + StatusReason string `gorm:"-" json:"status_reason,omitempty"` + FailureRate float64 `gorm:"-" json:"failure_rate,omitempty"` + History []SmartAtaAttribute `gorm:"-" json:"history,omitempty"` +} + +// compare the attribute (raw, normalized, transformed) value to observed thresholds, and update status if necessary +func (sa *SmartAtaAttribute) MetadataObservedThresholdStatus(smartMetadata metadata.AtaAttributeMetadata) { + //TODO: multiple rules + // try to predict the failure rates for observed thresholds that have 0 failure rate and error bars. + // - if the attribute is critical + // - the failure rate is over 10 - set to failed + // - the attribute does not match any threshold, set to warn + // - if the attribute is not critical + // - if failure rate is above 20 - set to failed + // - if failure rate is above 10 but below 20 - set to warn + + //update the smart attribute status based on Observed thresholds. + var value int64 + if smartMetadata.DisplayType == metadata.AtaSmartAttributeDisplayTypeNormalized { + value = int64(sa.Value) + } else if smartMetadata.DisplayType == metadata.AtaSmartAttributeDisplayTypeTransformed { + value = sa.TransformedValue + } else { + value = sa.RawValue + } + + for _, obsThresh := range smartMetadata.ObservedThresholds { + + //check if "value" is in this bucket + if ((obsThresh.Low == obsThresh.High) && value == obsThresh.Low) || + (obsThresh.Low < value && value <= obsThresh.High) { + sa.FailureRate = obsThresh.AnnualFailureRate + + if smartMetadata.Critical { + if obsThresh.AnnualFailureRate >= 0.10 { + sa.Status = SmartAttributeStatusFailed + sa.StatusReason = "Observed Failure Rate for Critical Attribute is greater than 10%" + } + } else { + if obsThresh.AnnualFailureRate >= 0.20 { + sa.Status = SmartAttributeStatusFailed + sa.StatusReason = "Observed Failure Rate for Attribute is greater than 20%" + } else if obsThresh.AnnualFailureRate >= 0.10 { + sa.Status = SmartAttributeStatusWarning + sa.StatusReason = "Observed Failure Rate for Attribute is greater than 10%" + } + } + + //we've found the correct bucket, we can drop out of this loop + return + } + } + // no bucket found + if smartMetadata.Critical { + sa.Status = SmartAttributeStatusWarning + sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute" + } + + return +} diff --git a/webapp/backend/pkg/models/db/smart_nvme_attribute.go b/webapp/backend/pkg/models/db/smart_nvme_attribute.go new file mode 100644 index 0000000..84623b5 --- /dev/null +++ b/webapp/backend/pkg/models/db/smart_nvme_attribute.go @@ -0,0 +1,21 @@ +package db + +import "github.com/jinzhu/gorm" + +type SmartNvmeAttribute struct { + gorm.Model + + SmartId int `json:"smart_id"` + Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key + + AttributeId string `json:"attribute_id"` //json string from smartctl + Name string `json:"name"` + Value int `json:"value"` + Threshold int `json:"thresh"` + + TransformedValue int64 `json:"transformed_value"` + Status string `gorm:"-" json:"status,omitempty"` + StatusReason string `gorm:"-" json:"status_reason,omitempty"` + FailureRate float64 `gorm:"-" json:"failure_rate,omitempty"` + History []SmartNvmeAttribute `gorm:"-" json:"history,omitempty"` +} diff --git a/webapp/backend/pkg/models/db/smart_scsci_attribute.go b/webapp/backend/pkg/models/db/smart_scsci_attribute.go new file mode 100644 index 0000000..0ee86ad --- /dev/null +++ b/webapp/backend/pkg/models/db/smart_scsci_attribute.go @@ -0,0 +1,21 @@ +package db + +import "github.com/jinzhu/gorm" + +type SmartScsiAttribute struct { + gorm.Model + + SmartId int `json:"smart_id"` + Smart Device `json:"-" gorm:"foreignkey:SmartId"` // use SmartId as foreign key + + AttributeId string `json:"attribute_id"` //json string from smartctl + Name string `json:"name"` + Value int `json:"value"` + Threshold int `json:"thresh"` + + TransformedValue int64 `json:"transformed_value"` + Status string `gorm:"-" json:"status,omitempty"` + StatusReason string `gorm:"-" json:"status_reason,omitempty"` + FailureRate float64 `gorm:"-" json:"failure_rate,omitempty"` + History []SmartScsiAttribute `gorm:"-" json:"history,omitempty"` +} diff --git a/webapp/backend/pkg/models/db/smart_test.go b/webapp/backend/pkg/models/db/smart_test.go index 1cefe2e..0f03715 100644 --- a/webapp/backend/pkg/models/db/smart_test.go +++ b/webapp/backend/pkg/models/db/smart_test.go @@ -29,14 +29,127 @@ func TestFromCollectorSmartInfo(t *testing.T) { //assert require.NoError(t, err) - require.Equal(t, smartMdl.DeviceWWN, "WWN-test") - require.Equal(t, smartMdl.SmartStatus, "passed") + require.Equal(t, "WWN-test", smartMdl.DeviceWWN) + require.Equal(t, "passed", smartMdl.SmartStatus) + require.Equal(t, 18, len(smartMdl.AtaAttributes)) + require.Equal(t, 0, len(smartMdl.NvmeAttributes)) + require.Equal(t, 0, len(smartMdl.ScsiAttributes)) //check that temperature was correctly parsed - for _, attr := range smartMdl.SmartAttributes { + for _, attr := range smartMdl.AtaAttributes { if attr.AttributeId == 194 { require.Equal(t, int64(163210330144), attr.RawValue) require.Equal(t, int64(32), attr.TransformedValue) } } } + +func TestFromCollectorSmartInfo_Fail(t *testing.T) { + //setup + smartDataFile, err := os.Open("../testdata/smart-fail.json") + require.NoError(t, err) + defer smartDataFile.Close() + + var smartJson collector.SmartInfo + + smartDataBytes, err := ioutil.ReadAll(smartDataFile) + require.NoError(t, err) + err = json.Unmarshal(smartDataBytes, &smartJson) + require.NoError(t, err) + + //test + smartMdl := db.Smart{} + err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson) + + //assert + require.NoError(t, err) + require.Equal(t, "WWN-test", smartMdl.DeviceWWN) + require.Equal(t, "failed", smartMdl.SmartStatus) + require.Equal(t, 0, len(smartMdl.AtaAttributes)) + require.Equal(t, 0, len(smartMdl.NvmeAttributes)) + require.Equal(t, 0, len(smartMdl.ScsiAttributes)) +} + +func TestFromCollectorSmartInfo_Fail2(t *testing.T) { + //setup + smartDataFile, err := os.Open("../testdata/smart-fail2.json") + require.NoError(t, err) + defer smartDataFile.Close() + + var smartJson collector.SmartInfo + + smartDataBytes, err := ioutil.ReadAll(smartDataFile) + require.NoError(t, err) + err = json.Unmarshal(smartDataBytes, &smartJson) + require.NoError(t, err) + + //test + smartMdl := db.Smart{} + err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson) + + //assert + require.NoError(t, err) + require.Equal(t, "WWN-test", smartMdl.DeviceWWN) + require.Equal(t, "failed", smartMdl.SmartStatus) + require.Equal(t, 17, len(smartMdl.AtaAttributes)) + require.Equal(t, 0, len(smartMdl.NvmeAttributes)) + require.Equal(t, 0, len(smartMdl.ScsiAttributes)) +} + +func TestFromCollectorSmartInfo_Nvme(t *testing.T) { + //setup + smartDataFile, err := os.Open("../testdata/smart-nvme.json") + require.NoError(t, err) + defer smartDataFile.Close() + + var smartJson collector.SmartInfo + + smartDataBytes, err := ioutil.ReadAll(smartDataFile) + require.NoError(t, err) + err = json.Unmarshal(smartDataBytes, &smartJson) + require.NoError(t, err) + + //test + smartMdl := db.Smart{} + err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson) + + //assert + require.NoError(t, err) + require.Equal(t, "WWN-test", smartMdl.DeviceWWN) + require.Equal(t, "passed", smartMdl.SmartStatus) + require.Equal(t, 0, len(smartMdl.AtaAttributes)) + require.Equal(t, 16, len(smartMdl.NvmeAttributes)) + require.Equal(t, 0, len(smartMdl.ScsiAttributes)) + + require.Equal(t, 111303174, smartMdl.NvmeAttributes[6].Value) + require.Equal(t, 83170961, smartMdl.NvmeAttributes[7].Value) +} + +func TestFromCollectorSmartInfo_Scsi(t *testing.T) { + //setup + smartDataFile, err := os.Open("../testdata/smart-scsi.json") + require.NoError(t, err) + defer smartDataFile.Close() + + var smartJson collector.SmartInfo + + smartDataBytes, err := ioutil.ReadAll(smartDataFile) + require.NoError(t, err) + err = json.Unmarshal(smartDataBytes, &smartJson) + require.NoError(t, err) + + //test + smartMdl := db.Smart{} + err = smartMdl.FromCollectorSmartInfo("WWN-test", smartJson) + + //assert + require.NoError(t, err) + require.Equal(t, "WWN-test", smartMdl.DeviceWWN) + require.Equal(t, "passed", smartMdl.SmartStatus) + require.Equal(t, 0, len(smartMdl.AtaAttributes)) + require.Equal(t, 0, len(smartMdl.NvmeAttributes)) + require.Equal(t, 13, len(smartMdl.ScsiAttributes)) + + require.Equal(t, 56, smartMdl.ScsiAttributes[0].Value) + require.Equal(t, 300357663, smartMdl.ScsiAttributes[4].Value) //total_errors_corrected +} diff --git a/webapp/backend/pkg/models/testdata/smart-megaraid0.json b/webapp/backend/pkg/models/testdata/smart-megaraid0.json new file mode 100644 index 0000000..5767206 --- /dev/null +++ b/webapp/backend/pkg/models/testdata/smart-megaraid0.json @@ -0,0 +1,697 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 1 + ], + "svn_revision": "5022", + "platform_info": "x86_64-linux-5.4.0-42-generic", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-a", + "-j", + "-d", + "megaraid,0", + "-i", + "/dev/sda" + ], + "messages": [ + { + "string": "Warning: This result is based on an Attribute check.", + "severity": "warning" + } + ], + "exit_status": 4 + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda [megaraid_disk_00] [SAT]", + "type": "sat+megaraid,0", + "protocol": "ATA" + }, + "model_name": "WD4000FYYX", + "serial_number": "XXXXXXXXXXXX", + "wwn": { + "naa": 5, + "oui": 5358, + "id": 10217451239 + }, + "ata_additional_product_id": "DELL(tm)", + "firmware_version": "00.0D1K4", + "user_capacity": { + "blocks": 7814037168, + "bytes": 4000787030016 + }, + "logical_block_size": 512, + "physical_block_size": 512, + "rotation_rate": 7200, + "form_factor": { + "ata_value": 2, + "name": "3.5 inches" + }, + "in_smartctl_database": false, + "ata_version": { + "string": "ATA8-ACS T13/1699-D revision 6", + "major_value": 510, + "minor_value": 40 + }, + "sata_version": { + "string": "SATA 3.0", + "value": 62 + }, + "interface_speed": { + "max": { + "sata_value": 6, + "string": "3.0 Gb/s", + "units_per_second": 30, + "bits_per_unit": 100000000 + }, + "current": { + "sata_value": 2, + "string": "3.0 Gb/s", + "units_per_second": 30, + "bits_per_unit": 100000000 + } + }, + "local_time": { + "time_t": 1598297918, + "asctime": "Mon Aug 24 21:38:38 2020 CEST" + }, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "offline_data_collection": { + "status": { + "value": 130, + "string": "was completed without error", + "passed": true + }, + "completion_seconds": 90 + }, + "self_test": { + "status": { + "value": 0, + "string": "completed without error", + "passed": true + }, + "polling_minutes": { + "short": 2, + "extended": 523, + "conveyance": 5 + } + }, + "capabilities": { + "values": [ + 123, + 3 + ], + "exec_offline_immediate_supported": true, + "offline_is_aborted_upon_new_cmd": false, + "offline_surface_scan_supported": true, + "self_tests_supported": true, + "conveyance_self_test_supported": true, + "selective_self_test_supported": true, + "attribute_autosave_enabled": true, + "error_logging_supported": true, + "gp_logging_supported": true + } + }, + "ata_sct_capabilities": { + "value": 28861, + "error_recovery_control_supported": true, + "feature_control_supported": true, + "data_table_supported": true + }, + "ata_smart_attributes": { + "revision": 16, + "table": [ + { + "id": 1, + "name": "Raw_Read_Error_Rate", + "value": 200, + "worst": 197, + "thresh": 51, + "when_failed": "", + "flags": { + "value": 47, + "string": "POSR-K ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 3, + "name": "Spin_Up_Time", + "value": 228, + "worst": 227, + "thresh": 21, + "when_failed": "", + "flags": { + "value": 39, + "string": "POS--K ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 7558, + "string": "7558" + } + }, + { + "id": 4, + "name": "Start_Stop_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 70, + "string": "70" + } + }, + { + "id": 5, + "name": "Reallocated_Sector_Ct", + "value": 200, + "worst": 200, + "thresh": 140, + "when_failed": "", + "flags": { + "value": 51, + "string": "PO--CK ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 7, + "name": "Seek_Error_Rate", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 46, + "string": "-OSR-K ", + "prefailure": false, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 9, + "name": "Power_On_Hours", + "value": 49, + "worst": 49, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 37787, + "string": "37787" + } + }, + { + "id": 10, + "name": "Spin_Retry_Count", + "value": 100, + "worst": 253, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 11, + "name": "Calibration_Retry_Count", + "value": 100, + "worst": 253, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 12, + "name": "Power_Cycle_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 70, + "string": "70" + } + }, + { + "id": 183, + "name": "Runtime_Bad_Block", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 192, + "name": "Power-Off_Retract_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 55, + "string": "55" + } + }, + { + "id": 193, + "name": "Load_Cycle_Count", + "value": 197, + "worst": 197, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 9267, + "string": "9267" + } + }, + { + "id": 194, + "name": "Temperature_Celsius", + "value": 116, + "worst": 104, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 34, + "string": "-O---K ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 3145764, + "string": "36 (Min/Max 0/48)" + } + }, + { + "id": 196, + "name": "Reallocated_Event_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 197, + "name": "Current_Pending_Sector", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 198, + "name": "Offline_Uncorrectable", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 48, + "string": "----CK ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 199, + "name": "UDMA_CRC_Error_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 200, + "name": "Multi_Zone_Error_Rate", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 8, + "string": "---R-- ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 241, + "name": "Total_LBAs_Written", + "value": 198, + "worst": 198, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 2754608750246, + "string": "2754608750246" + } + }, + { + "id": 242, + "name": "Total_LBAs_Read", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 70057180117, + "string": "70057180117" + } + } + ] + }, + "power_on_time": { + "hours": 37787 + }, + "power_cycle_count": 70, + "temperature": { + "current": 36 + }, + "ata_smart_error_log": { + "summary": { + "revision": 1, + "count": 0 + } + }, + "ata_smart_self_test_log": { + "standard": { + "revision": 1, + "table": [ + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 35990 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 3 + }, + { + "type": { + "value": 223, + "string": "Vendor (0xdf)" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 3 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1 + } + ], + "count": 4, + "error_count_total": 0, + "error_count_outdated": 0 + } + }, + "ata_smart_selective_self_test_log": { + "revision": 1, + "table": [ + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + } + ], + "flags": { + "value": 0, + "remainder_scan_enabled": false + }, + "power_up_scan_resume_minutes": 0 + } +} diff --git a/webapp/backend/pkg/models/testdata/smart-megaraid1.json b/webapp/backend/pkg/models/testdata/smart-megaraid1.json new file mode 100644 index 0000000..04a2af2 --- /dev/null +++ b/webapp/backend/pkg/models/testdata/smart-megaraid1.json @@ -0,0 +1,709 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 1 + ], + "svn_revision": "5022", + "platform_info": "x86_64-linux-5.4.0-42-generic", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-a", + "-j", + "-d", + "megaraid,1", + "-i", + "/dev/sda" + ], + "messages": [ + { + "string": "Warning: This result is based on an Attribute check.", + "severity": "warning" + } + ], + "exit_status": 4 + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda [megaraid_disk_01] [SAT]", + "type": "sat+megaraid,1", + "protocol": "ATA" + }, + "model_name": "WD4000FYYX", + "serial_number": "XXXXXXXXXXXX", + "wwn": { + "naa": 5, + "oui": 5358, + "id": 11649125727 + }, + "ata_additional_product_id": "DELL(tm)", + "firmware_version": "00.0D1K4", + "user_capacity": { + "blocks": 7814037168, + "bytes": 4000787030016 + }, + "logical_block_size": 512, + "physical_block_size": 512, + "rotation_rate": 7200, + "form_factor": { + "ata_value": 2, + "name": "3.5 inches" + }, + "in_smartctl_database": false, + "ata_version": { + "string": "ATA8-ACS T13/1699-D revision 6", + "major_value": 510, + "minor_value": 40 + }, + "sata_version": { + "string": "SATA 3.0", + "value": 62 + }, + "interface_speed": { + "max": { + "sata_value": 6, + "string": "3.0 Gb/s", + "units_per_second": 30, + "bits_per_unit": 100000000 + }, + "current": { + "sata_value": 2, + "string": "3.0 Gb/s", + "units_per_second": 30, + "bits_per_unit": 100000000 + } + }, + "local_time": { + "time_t": 1598297922, + "asctime": "Mon Aug 24 21:38:42 2020 CEST" + }, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "offline_data_collection": { + "status": { + "value": 130, + "string": "was completed without error", + "passed": true + }, + "completion_seconds": 90 + }, + "self_test": { + "status": { + "value": 0, + "string": "completed without error", + "passed": true + }, + "polling_minutes": { + "short": 2, + "extended": 503, + "conveyance": 5 + } + }, + "capabilities": { + "values": [ + 123, + 3 + ], + "exec_offline_immediate_supported": true, + "offline_is_aborted_upon_new_cmd": false, + "offline_surface_scan_supported": true, + "self_tests_supported": true, + "conveyance_self_test_supported": true, + "selective_self_test_supported": true, + "attribute_autosave_enabled": true, + "error_logging_supported": true, + "gp_logging_supported": true + } + }, + "ata_sct_capabilities": { + "value": 28861, + "error_recovery_control_supported": true, + "feature_control_supported": true, + "data_table_supported": true + }, + "ata_smart_attributes": { + "revision": 16, + "table": [ + { + "id": 1, + "name": "Raw_Read_Error_Rate", + "value": 200, + "worst": 111, + "thresh": 51, + "when_failed": "", + "flags": { + "value": 47, + "string": "POSR-K ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 3, + "name": "Spin_Up_Time", + "value": 230, + "worst": 227, + "thresh": 21, + "when_failed": "", + "flags": { + "value": 39, + "string": "POS--K ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 7458, + "string": "7458" + } + }, + { + "id": 4, + "name": "Start_Stop_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 68, + "string": "68" + } + }, + { + "id": 5, + "name": "Reallocated_Sector_Ct", + "value": 188, + "worst": 188, + "thresh": 140, + "when_failed": "", + "flags": { + "value": 51, + "string": "PO--CK ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 387, + "string": "387" + } + }, + { + "id": 7, + "name": "Seek_Error_Rate", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 46, + "string": "-OSR-K ", + "prefailure": false, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 9, + "name": "Power_On_Hours", + "value": 49, + "worst": 49, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 37788, + "string": "37788" + } + }, + { + "id": 10, + "name": "Spin_Retry_Count", + "value": 100, + "worst": 253, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 11, + "name": "Calibration_Retry_Count", + "value": 100, + "worst": 253, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 12, + "name": "Power_Cycle_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 68, + "string": "68" + } + }, + { + "id": 183, + "name": "Runtime_Bad_Block", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 192, + "name": "Power-Off_Retract_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 56, + "string": "56" + } + }, + { + "id": 193, + "name": "Load_Cycle_Count", + "value": 197, + "worst": 197, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 9462, + "string": "9462" + } + }, + { + "id": 194, + "name": "Temperature_Celsius", + "value": 116, + "worst": 101, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 34, + "string": "-O---K ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 3342372, + "string": "36 (Min/Max 0/51)" + } + }, + { + "id": 196, + "name": "Reallocated_Event_Count", + "value": 191, + "worst": 191, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 9, + "string": "9" + } + }, + { + "id": 197, + "name": "Current_Pending_Sector", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 198, + "name": "Offline_Uncorrectable", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 48, + "string": "----CK ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 199, + "name": "UDMA_CRC_Error_Count", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 200, + "name": "Multi_Zone_Error_Rate", + "value": 200, + "worst": 199, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 8, + "string": "---R-- ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 241, + "name": "Total_LBAs_Written", + "value": 197, + "worst": 197, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 3920560799278, + "string": "3920560799278" + } + }, + { + "id": 242, + "name": "Total_LBAs_Read", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 72684827907, + "string": "72684827907" + } + } + ] + }, + "power_on_time": { + "hours": 37788 + }, + "power_cycle_count": 68, + "temperature": { + "current": 36 + }, + "ata_smart_error_log": { + "summary": { + "revision": 1, + "count": 0 + } + }, + "ata_smart_self_test_log": { + "standard": { + "revision": 1, + "table": [ + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 25, + "string": "Aborted by host", + "remaining_percent": 90 + }, + "lifetime_hours": 35990 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 35990 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 3 + }, + { + "type": { + "value": 223, + "string": "Vendor (0xdf)" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 3 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1 + } + ], + "count": 5, + "error_count_total": 0, + "error_count_outdated": 0 + } + }, + "ata_smart_selective_self_test_log": { + "revision": 1, + "table": [ + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 0, + "string": "Not_testing" + } + } + ], + "flags": { + "value": 0, + "remainder_scan_enabled": false + }, + "power_up_scan_resume_minutes": 0 + } +} diff --git a/webapp/backend/pkg/models/testdata/smart-scsi2.json b/webapp/backend/pkg/models/testdata/smart-scsi2.json new file mode 100644 index 0000000..26f9470 --- /dev/null +++ b/webapp/backend/pkg/models/testdata/smart-scsi2.json @@ -0,0 +1,88 @@ +{ + "json_format_version": [ + 0, + 1 + ], + "smartctl": { + "version": [ + 6, + 7 + ], + "platform_info": "x86_64-linux-4.4.0-138-generic", + "build_info": "(local build)", + "argv": [ + "smartctl", + "/dev/sdb", + "-ja" + ], + "exit_status": 0 + }, + "device": { + "name": "/dev/sdb", + "info_name": "/dev/sdb", + "type": "scsi", + "protocol": "SCSI" + }, + "vendor": "SEAGATE", + "product": "ST1200MM0088", + "model_name": "SEAGATE ST1200MM0088", + "revision": "N004", + "scsi_version": "SPC-4", + "user_capacity": { + "blocks": 2344225968, + "bytes": 1200243695616 + }, + "logical_block_size": 512, + "rotation_rate": 10500, + "form_factor": { + "scsi_value": 3, + "name": "2.5 inches" + }, + "serial_number": "Z4028VRY0000C810BZXB", + "device_type": { + "scsi_value": 0, + "name": "disk" + }, + "local_time": { + "time_t": 1545001755, + "asctime": "Sun Dec 16 17:09:15 2018 CST" + }, + "smart_status": { + "passed": true + }, + "format_status": { + "grown_defects_during_cert": "not_available", + "blocks_reassigned_during_format": "not_available", + "total_new_block_since_format": "not_available", + "power_on_minutes_since_format": "not_available" + }, + "temperature": { + "current": 31, + "drive_trip": 60 + }, + "scsi_grown_defect_list": 0, + "power_on_time": { + "hours": 5675, + "minutes": 39 + }, + "scsi_error_counter_log": { + "read": { + "errors_corrected_by_eccfast": 1410362924, + "errors_corrected_by_eccdelayed": 0, + "errors_corrected_by_rereads_rewrites": 0, + "total_errors_corrected": 1410362924, + "correction_algorithm_invocations": 0, + "gigabytes_processed": "386.568", + "total_uncorrected_errors": 0 + }, + "write": { + "errors_corrected_by_eccfast": 0, + "errors_corrected_by_eccdelayed": 0, + "errors_corrected_by_rereads_rewrites": 0, + "total_errors_corrected": 0, + "correction_algorithm_invocations": 0, + "gigabytes_processed": "806.827", + "total_uncorrected_errors": 0 + } + } +} diff --git a/webapp/backend/pkg/web/handler/get_device_details.go b/webapp/backend/pkg/web/handler/get_device_details.go index 8c7980c..cd45a32 100644 --- a/webapp/backend/pkg/web/handler/get_device_details.go +++ b/webapp/backend/pkg/web/handler/get_device_details.go @@ -16,12 +16,23 @@ func GetDeviceDetails(c *gin.Context) { Preload("SmartResults", func(db *gorm.DB) *gorm.DB { return db.Order("smarts.created_at DESC").Limit(40) }). - Preload("SmartResults.SmartAttributes"). + Preload("SmartResults.AtaAttributes"). + Preload("SmartResults.NvmeAttributes"). + Preload("SmartResults.ScsiAttributes"). Where("wwn = ?", c.Param("wwn")). First(&device) device.SquashHistory() device.ApplyMetadataRules() - c.JSON(http.StatusOK, gin.H{"success": true, "data": device, "lookup": metadata.AtaSmartAttributes}) + var deviceMetadata interface{} + if device.IsAta() { + deviceMetadata = metadata.AtaMetadata + } else if device.IsNvme() { + deviceMetadata = metadata.NmveMetadata + } else if device.IsScsi() { + deviceMetadata = metadata.ScsiMetadata + } + + c.JSON(http.StatusOK, gin.H{"success": true, "data": device, "metadata": deviceMetadata}) } diff --git a/webapp/backend/pkg/web/server.go b/webapp/backend/pkg/web/server.go index 7789db1..9d89dab 100644 --- a/webapp/backend/pkg/web/server.go +++ b/webapp/backend/pkg/web/server.go @@ -13,7 +13,7 @@ type AppEngine struct { Config config.Interface } -func (ae *AppEngine) Start() error { +func (ae *AppEngine) Setup() *gin.Engine { r := gin.Default() r.Use(database.DatabaseHandler(ae.Config.GetString("web.database.location"))) @@ -46,6 +46,11 @@ func (ae *AppEngine) Start() error { r.NoRoute(func(c *gin.Context) { c.File(fmt.Sprintf("%s/index.html", ae.Config.GetString("web.src.frontend.path"))) }) + return r +} + +func (ae *AppEngine) Start() error { + r := ae.Setup() return r.Run(fmt.Sprintf("%s:%s", ae.Config.GetString("web.listen.host"), ae.Config.GetString("web.listen.port"))) } diff --git a/webapp/backend/pkg/web/server_test.go b/webapp/backend/pkg/web/server_test.go new file mode 100644 index 0000000..1d6d205 --- /dev/null +++ b/webapp/backend/pkg/web/server_test.go @@ -0,0 +1,149 @@ +package web_test + +import ( + mock_config "github.com/analogj/scrutiny/webapp/backend/pkg/config/mock" + "github.com/analogj/scrutiny/webapp/backend/pkg/web" + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "os" + "testing" +) + +func TestHealthRoute(t *testing.T) { + //setup + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + fakeConfig := mock_config.NewMockInterface(mockCtrl) + fakeConfig.EXPECT().GetString("web.database.location").Return("testdata/scrutiny_test.db") + fakeConfig.EXPECT().GetString("web.src.frontend.path").Return("testdata") + + ae := web.AppEngine{ + Config: fakeConfig, + } + + router := ae.Setup() + + //test + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/health", nil) + router.ServeHTTP(w, req) + + //assert + require.Equal(t, 200, w.Code) + require.Equal(t, "{\"success\":true}", w.Body.String()) +} + +func TestRegisterDevicesRoute(t *testing.T) { + //setup + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + fakeConfig := mock_config.NewMockInterface(mockCtrl) + fakeConfig.EXPECT().GetString("web.database.location").Return("testdata/scrutiny_test.db") + fakeConfig.EXPECT().GetString("web.src.frontend.path").Return("testdata") + ae := web.AppEngine{ + Config: fakeConfig, + } + router := ae.Setup() + file, err := os.Open("testdata/register-devices-req.json") + require.NoError(t, err) + + //test + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/devices/register", file) + router.ServeHTTP(w, req) + + //assert + require.Equal(t, 200, w.Code) +} + +func TestUploadDeviceMetricsRoute(t *testing.T) { + //setup + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + fakeConfig := mock_config.NewMockInterface(mockCtrl) + fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return("testdata/scrutiny_test.db") + fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return("testdata") + ae := web.AppEngine{ + Config: fakeConfig, + } + router := ae.Setup() + devicesfile, err := os.Open("testdata/register-devices-single-req.json") + require.NoError(t, err) + + metricsfile, err := os.Open("testdata/upload-device-metrics-req.json") + require.NoError(t, err) + + //test + wr := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/devices/register", devicesfile) + router.ServeHTTP(wr, req) + require.Equal(t, 200, wr.Code) + + mr := httptest.NewRecorder() + req, _ = http.NewRequest("POST", "/api/device/0x5000cca264eb01d7/smart", metricsfile) + router.ServeHTTP(mr, req) + require.Equal(t, 200, mr.Code) + + //assert +} + +func TestPopulateMultiple(t *testing.T) { + //setup + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + fakeConfig := mock_config.NewMockInterface(mockCtrl) + fakeConfig.EXPECT().GetString("web.database.location").AnyTimes().Return("testdata/scrutiny_test.db") + fakeConfig.EXPECT().GetString("web.src.frontend.path").AnyTimes().Return("testdata") + ae := web.AppEngine{ + Config: fakeConfig, + } + router := ae.Setup() + devicesfile, err := os.Open("testdata/register-devices-req.json") + require.NoError(t, err) + + metricsfile, err := os.Open("../models/testdata/smart-ata.json") + require.NoError(t, err) + failfile, err := os.Open("../models/testdata/smart-fail2.json") + require.NoError(t, err) + nvmefile, err := os.Open("../models/testdata/smart-nvme.json") + require.NoError(t, err) + scsifile, err := os.Open("../models/testdata/smart-scsi.json") + require.NoError(t, err) + scsi2file, err := os.Open("../models/testdata/smart-scsi2.json") + require.NoError(t, err) + + //test + wr := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/devices/register", devicesfile) + router.ServeHTTP(wr, req) + require.Equal(t, 200, wr.Code) + + mr := httptest.NewRecorder() + req, _ = http.NewRequest("POST", "/api/device/0x5000cca264eb01d7/smart", metricsfile) + router.ServeHTTP(mr, req) + require.Equal(t, 200, mr.Code) + + fr := httptest.NewRecorder() + req, _ = http.NewRequest("POST", "/api/device/0x5000cca264ec3183/smart", failfile) + router.ServeHTTP(fr, req) + require.Equal(t, 200, fr.Code) + + nr := httptest.NewRecorder() + req, _ = http.NewRequest("POST", "/api/device/0x5002538e40a22954/smart", nvmefile) + router.ServeHTTP(nr, req) + require.Equal(t, 200, nr.Code) + + sr := httptest.NewRecorder() + req, _ = http.NewRequest("POST", "/api/device/0x5000cca252c859cc/smart", scsifile) + router.ServeHTTP(sr, req) + require.Equal(t, 200, sr.Code) + + s2r := httptest.NewRecorder() + req, _ = http.NewRequest("POST", "/api/device/0x5000cca264ebc248/smart", scsi2file) + router.ServeHTTP(s2r, req) + require.Equal(t, 200, s2r.Code) + + //assert +} diff --git a/webapp/backend/pkg/web/testdata/register-devices-req.json b/webapp/backend/pkg/web/testdata/register-devices-req.json new file mode 100644 index 0000000..34d4b45 --- /dev/null +++ b/webapp/backend/pkg/web/testdata/register-devices-req.json @@ -0,0 +1,102 @@ +{ + "data": [ + { + "wwn": "0x5002538e40a22954", + "device_name": "sda", + "manufacturer": "ATA", + "model_name": "Samsung_SSD_860_EVO_500GB", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "S3YZNB0KBXXXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 500107862016, + "form_factor": "", + "smart_support": false + }, + { + "wwn": "0x5000cca264eb01d7", + "device_name": "sdb", + "manufacturer": "ATA", + "model_name": "WDC_WD140EDFZ-11A0VA0", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "9RK1XXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 14000519643136, + "form_factor": "", + "smart_support": false + }, + { + "wwn": "0x5000cca264ec3183", + "device_name": "sdc", + "manufacturer": "ATA", + "model_name": "WDC_WD140EDFZ-11A0VA0", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "9RK4XXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 14000519643136, + "form_factor": "", + "smart_support": false + }, + { + "wwn": "0x5000cca252c859cc", + "device_name": "sdd", + "manufacturer": "ATA", + "model_name": "WDC_WD80EFAX-68LHPN0", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "7SGLXXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 8001563222016, + "form_factor": "", + "smart_support": false + }, + { + "wwn": "0x5000cca264ebc248", + "device_name": "sde", + "manufacturer": "ATA", + "model_name": "WDC_WD140EDFZ-11A0VA0", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "9RK3XXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 14000519643136, + "form_factor": "", + "smart_support": false + }, + { + "wwn": "0x50014ee20b2a72a9", + "device_name": "sdf", + "manufacturer": "ATA", + "model_name": "WDC_WD60EFRX-68MYMN1", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "WD-WXL1HXXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 6001175126016, + "form_factor": "", + "smart_support": false + }, + { + "wwn": "0x5000c500673e6b5f", + "device_name": "sdg", + "manufacturer": "ATA", + "model_name": "ST6000DX000-1H217Z", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "Z4DXXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 6001175126016, + "form_factor": "", + "smart_support": false + } + ] +} diff --git a/webapp/backend/pkg/web/testdata/register-devices-single-req.json b/webapp/backend/pkg/web/testdata/register-devices-single-req.json new file mode 100644 index 0000000..84aac6b --- /dev/null +++ b/webapp/backend/pkg/web/testdata/register-devices-single-req.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "wwn": "0x5000cca264eb01d7", + "device_name": "sdb", + "manufacturer": "ATA", + "model_name": "WDC_WD140EDFZ-11A0VA0", + "interface_type": "SCSI", + "interface_speed": "", + "serial_number": "9RK1XXXXX", + "firmware": "", + "rotational_speed": 0, + "capacity": 14000519643136, + "form_factor": "", + "smart_support": false + } + ] +} diff --git a/webapp/backend/pkg/web/testdata/upload-device-metrics-req.json b/webapp/backend/pkg/web/testdata/upload-device-metrics-req.json new file mode 100644 index 0000000..36ac36b --- /dev/null +++ b/webapp/backend/pkg/web/testdata/upload-device-metrics-req.json @@ -0,0 +1,846 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 0 + ], + "svn_revision": "4883", + "platform_info": "x86_64-linux-4.19.128-flatcar", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-j", + "-a", + "/dev/sdb" + ], + "exit_status": 0 + }, + "device": { + "name": "/dev/sdb", + "info_name": "/dev/sdb [SAT]", + "type": "sat", + "protocol": "ATA" + }, + "model_name": "WDC WD140EDFZ-11A0VA0", + "serial_number": "9RK1XXXX", + "wwn": { + "naa": 5, + "oui": 3274, + "id": 10283057623 + }, + "firmware_version": "81.00A81", + "user_capacity": { + "blocks": 27344764928, + "bytes": 14000519643136 + }, + "logical_block_size": 512, + "physical_block_size": 4096, + "rotation_rate": 5400, + "form_factor": { + "ata_value": 2, + "name": "3.5 inches" + }, + "in_smartctl_database": false, + "ata_version": { + "string": "ACS-2, ATA8-ACS T13/1699-D revision 4", + "major_value": 1020, + "minor_value": 41 + }, + "sata_version": { + "string": "SATA 3.2", + "value": 255 + }, + "interface_speed": { + "max": { + "sata_value": 14, + "string": "6.0 Gb/s", + "units_per_second": 60, + "bits_per_unit": 100000000 + }, + "current": { + "sata_value": 3, + "string": "6.0 Gb/s", + "units_per_second": 60, + "bits_per_unit": 100000000 + } + }, + "local_time": { + "time_t": 1592697810, + "asctime": "Sun Jun 21 00:03:30 2020 UTC" + }, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "offline_data_collection": { + "status": { + "value": 130, + "string": "was completed without error", + "passed": true + }, + "completion_seconds": 101 + }, + "self_test": { + "status": { + "value": 241, + "string": "in progress, 10% remaining", + "remaining_percent": 10 + }, + "polling_minutes": { + "short": 2, + "extended": 1479 + } + }, + "capabilities": { + "values": [ + 91, + 3 + ], + "exec_offline_immediate_supported": true, + "offline_is_aborted_upon_new_cmd": false, + "offline_surface_scan_supported": true, + "self_tests_supported": true, + "conveyance_self_test_supported": false, + "selective_self_test_supported": true, + "attribute_autosave_enabled": true, + "error_logging_supported": true, + "gp_logging_supported": true + } + }, + "ata_sct_capabilities": { + "value": 61, + "error_recovery_control_supported": true, + "feature_control_supported": true, + "data_table_supported": true + }, + "ata_smart_attributes": { + "revision": 16, + "table": [ + { + "id": 1, + "name": "Raw_Read_Error_Rate", + "value": 100, + "worst": 100, + "thresh": 1, + "when_failed": "", + "flags": { + "value": 11, + "string": "PO-R-- ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 2, + "name": "Throughput_Performance", + "value": 135, + "worst": 135, + "thresh": 54, + "when_failed": "", + "flags": { + "value": 4, + "string": "--S--- ", + "prefailure": false, + "updated_online": false, + "performance": true, + "error_rate": false, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 108, + "string": "108" + } + }, + { + "id": 3, + "name": "Spin_Up_Time", + "value": 81, + "worst": 81, + "thresh": 1, + "when_failed": "", + "flags": { + "value": 7, + "string": "POS--- ", + "prefailure": true, + "updated_online": true, + "performance": true, + "error_rate": false, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 30089675132, + "string": "380 (Average 380)" + } + }, + { + "id": 4, + "name": "Start_Stop_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 18, + "string": "-O--C- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 9, + "string": "9" + } + }, + { + "id": 5, + "name": "Reallocated_Sector_Ct", + "value": 100, + "worst": 100, + "thresh": 1, + "when_failed": "", + "flags": { + "value": 51, + "string": "PO--CK ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 7, + "name": "Seek_Error_Rate", + "value": 100, + "worst": 100, + "thresh": 1, + "when_failed": "", + "flags": { + "value": 10, + "string": "-O-R-- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 8, + "name": "Seek_Time_Performance", + "value": 133, + "worst": 133, + "thresh": 20, + "when_failed": "", + "flags": { + "value": 4, + "string": "--S--- ", + "prefailure": false, + "updated_online": false, + "performance": true, + "error_rate": false, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 18, + "string": "18" + } + }, + { + "id": 9, + "name": "Power_On_Hours", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 18, + "string": "-O--C- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 1730, + "string": "1730" + } + }, + { + "id": 10, + "name": "Spin_Retry_Count", + "value": 100, + "worst": 100, + "thresh": 1, + "when_failed": "", + "flags": { + "value": 18, + "string": "-O--C- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 12, + "name": "Power_Cycle_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 9, + "string": "9" + } + }, + { + "id": 22, + "name": "Unknown_Attribute", + "value": 100, + "worst": 100, + "thresh": 25, + "when_failed": "", + "flags": { + "value": 35, + "string": "PO---K ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 100, + "string": "100" + } + }, + { + "id": 192, + "name": "Power-Off_Retract_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 329, + "string": "329" + } + }, + { + "id": 193, + "name": "Load_Cycle_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 18, + "string": "-O--C- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 329, + "string": "329" + } + }, + { + "id": 194, + "name": "Temperature_Celsius", + "value": 51, + "worst": 51, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 2, + "string": "-O---- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 163210330144, + "string": "32 (Min/Max 24/38)" + } + }, + { + "id": 196, + "name": "Reallocated_Event_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 50, + "string": "-O--CK ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 197, + "name": "Current_Pending_Sector", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 34, + "string": "-O---K ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": false, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 198, + "name": "Offline_Uncorrectable", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 8, + "string": "---R-- ", + "prefailure": false, + "updated_online": false, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 199, + "name": "UDMA_CRC_Error_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 10, + "string": "-O-R-- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": true, + "event_count": false, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + } + ] + }, + "power_on_time": { + "hours": 1730 + }, + "power_cycle_count": 9, + "temperature": { + "current": 32 + }, + "ata_smart_error_log": { + "summary": { + "revision": 1, + "count": 0 + } + }, + "ata_smart_self_test_log": { + "standard": { + "revision": 1, + "table": [ + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1708 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1684 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1661 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1636 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1624 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1541 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1517 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1493 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1469 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1445 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1439 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1373 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1349 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1325 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1301 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1277 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1253 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1252 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1205 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1181 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 1157 + } + ], + "count": 21, + "error_count_total": 0, + "error_count_outdated": 0 + } + }, + "ata_smart_selective_self_test_log": { + "revision": 1, + "table": [ + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 241, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 241, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 241, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 241, + "string": "Not_testing" + } + }, + { + "lba_min": 0, + "lba_max": 0, + "status": { + "value": 241, + "string": "Not_testing" + } + } + ], + "flags": { + "value": 0, + "remainder_scan_enabled": false + }, + "power_up_scan_resume_minutes": 0 + } +} diff --git a/webapp/frontend/src/app/data/mock/device/details/data.ts b/webapp/frontend/src/app/data/mock/device/details/data.ts index 6100b34..afd47e5 100644 --- a/webapp/frontend/src/app/data/mock/device/details/data.ts +++ b/webapp/frontend/src/app/data/mock/device/details/data.ts @@ -31,7 +31,7 @@ export const details = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": [ + "ata_attributes": [ { "ID": 2632, "CreatedAt": "2020-08-14T03:41:00.763627397Z", diff --git a/webapp/frontend/src/app/data/mock/summary/data.ts b/webapp/frontend/src/app/data/mock/summary/data.ts index 09d11c0..3a064ed 100644 --- a/webapp/frontend/src/app/data/mock/summary/data.ts +++ b/webapp/frontend/src/app/data/mock/summary/data.ts @@ -51,7 +51,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 137, @@ -64,7 +64,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 129, @@ -77,7 +77,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 123, @@ -90,7 +90,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 119, @@ -103,7 +103,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 106, @@ -116,7 +116,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 99, @@ -129,7 +129,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 92, @@ -142,7 +142,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 85, @@ -155,7 +155,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 78, @@ -168,7 +168,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 71, @@ -181,7 +181,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 64, @@ -194,7 +194,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 57, @@ -207,7 +207,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 50, @@ -220,7 +220,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 43, @@ -233,7 +233,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 36, @@ -246,7 +246,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 29, @@ -259,7 +259,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 27, @@ -272,7 +272,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 15, @@ -285,7 +285,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 12, @@ -298,7 +298,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null }, { "ID": 1, @@ -311,7 +311,7 @@ export const summary = { "temp": 36, "power_on_hours": 13818, "power_cycle_count": 13, - "smart_attributes": null + "ata_attributes": null } ] }, @@ -344,7 +344,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 138, @@ -357,7 +357,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 128, @@ -370,7 +370,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 120, @@ -383,7 +383,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 114, @@ -396,7 +396,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 110, @@ -409,7 +409,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 103, @@ -422,7 +422,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 97, @@ -435,7 +435,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 90, @@ -448,7 +448,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 80, @@ -461,7 +461,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 73, @@ -474,7 +474,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 69, @@ -487,7 +487,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 61, @@ -500,7 +500,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 54, @@ -513,7 +513,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 48, @@ -526,7 +526,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 39, @@ -539,7 +539,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 32, @@ -552,7 +552,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 26, @@ -565,7 +565,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 18, @@ -578,7 +578,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 10, @@ -591,7 +591,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 3, @@ -604,7 +604,7 @@ export const summary = { "temp": 34, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null } ] }, @@ -637,7 +637,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 139, @@ -650,7 +650,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 131, @@ -663,7 +663,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 124, @@ -676,7 +676,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 116, @@ -689,7 +689,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 109, @@ -702,7 +702,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 100, @@ -715,7 +715,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 95, @@ -728,7 +728,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 87, @@ -741,7 +741,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 79, @@ -754,7 +754,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 74, @@ -767,7 +767,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 65, @@ -780,7 +780,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 60, @@ -793,7 +793,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 51, @@ -806,7 +806,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 47, @@ -819,7 +819,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 38, @@ -832,7 +832,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 34, @@ -845,7 +845,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 24, @@ -858,7 +858,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 16, @@ -871,7 +871,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 8, @@ -884,7 +884,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 2, @@ -897,7 +897,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null } ] }, @@ -930,7 +930,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 135, @@ -943,7 +943,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 130, @@ -956,7 +956,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 125, @@ -969,7 +969,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 113, @@ -982,7 +982,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 111, @@ -995,7 +995,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 102, @@ -1008,7 +1008,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 94, @@ -1021,7 +1021,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 88, @@ -1034,7 +1034,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 83, @@ -1047,7 +1047,7 @@ export const summary = { "temp": 30, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 72, @@ -1060,7 +1060,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 66, @@ -1073,7 +1073,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 59, @@ -1086,7 +1086,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 55, @@ -1099,7 +1099,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 45, @@ -1112,7 +1112,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 40, @@ -1125,7 +1125,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 30, @@ -1138,7 +1138,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 23, @@ -1151,7 +1151,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 20, @@ -1164,7 +1164,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 11, @@ -1177,7 +1177,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null }, { "ID": 7, @@ -1190,7 +1190,7 @@ export const summary = { "temp": 29, "power_on_hours": 24996, "power_cycle_count": 42, - "smart_attributes": null + "ata_attributes": null } ] }, @@ -1223,7 +1223,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 136, @@ -1236,7 +1236,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 132, @@ -1249,7 +1249,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 122, @@ -1262,7 +1262,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 118, @@ -1275,7 +1275,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 108, @@ -1288,7 +1288,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 101, @@ -1301,7 +1301,7 @@ export const summary = { "temp": 33, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 96, @@ -1314,7 +1314,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 86, @@ -1327,7 +1327,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 81, @@ -1340,7 +1340,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 75, @@ -1353,7 +1353,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 67, @@ -1366,7 +1366,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 62, @@ -1379,7 +1379,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 53, @@ -1392,7 +1392,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 46, @@ -1405,7 +1405,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 41, @@ -1418,7 +1418,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 31, @@ -1431,7 +1431,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 22, @@ -1444,7 +1444,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 17, @@ -1457,7 +1457,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 9, @@ -1470,7 +1470,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null }, { "ID": 4, @@ -1483,7 +1483,7 @@ export const summary = { "temp": 32, "power_on_hours": 3030, "power_cycle_count": 9, - "smart_attributes": null + "ata_attributes": null } ] }, @@ -1516,7 +1516,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 134, @@ -1529,7 +1529,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 127, @@ -1542,7 +1542,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 121, @@ -1555,7 +1555,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 115, @@ -1568,7 +1568,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 107, @@ -1581,7 +1581,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 104, @@ -1594,7 +1594,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 93, @@ -1607,7 +1607,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 89, @@ -1620,7 +1620,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 82, @@ -1633,7 +1633,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 76, @@ -1646,7 +1646,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 68, @@ -1659,7 +1659,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 58, @@ -1672,7 +1672,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 52, @@ -1685,7 +1685,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 44, @@ -1698,7 +1698,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 37, @@ -1711,7 +1711,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 33, @@ -1724,7 +1724,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 25, @@ -1737,7 +1737,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 19, @@ -1750,7 +1750,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 13, @@ -1763,7 +1763,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null }, { "ID": 5, @@ -1776,7 +1776,7 @@ export const summary = { "temp": 30, "power_on_hours": 46949, "power_cycle_count": 54, - "smart_attributes": null + "ata_attributes": null } ] }, @@ -1809,7 +1809,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 140, @@ -1822,7 +1822,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 133, @@ -1835,7 +1835,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 126, @@ -1848,7 +1848,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 117, @@ -1861,7 +1861,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 112, @@ -1874,7 +1874,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 105, @@ -1887,7 +1887,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 98, @@ -1900,7 +1900,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 91, @@ -1913,7 +1913,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 84, @@ -1926,7 +1926,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 77, @@ -1939,7 +1939,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 70, @@ -1952,7 +1952,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 63, @@ -1965,7 +1965,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 56, @@ -1978,7 +1978,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 49, @@ -1991,7 +1991,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 42, @@ -2004,7 +2004,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 35, @@ -2017,7 +2017,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 28, @@ -2030,7 +2030,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 21, @@ -2043,7 +2043,7 @@ export const summary = { "temp": 35, "power_on_hours": 48706, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 14, @@ -2056,7 +2056,7 @@ export const summary = { "temp": 35, "power_on_hours": 48705, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null }, { "ID": 6, @@ -2069,7 +2069,7 @@ export const summary = { "temp": 35, "power_on_hours": 48705, "power_cycle_count": 69, - "smart_attributes": null + "ata_attributes": null } ] } diff --git a/webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html b/webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html index 4817b6a..09212ac 100644 --- a/webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html +++ b/webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html @@ -51,8 +51,9 @@
-
-
+
+
/dev/{{disk.device_name}} - {{disk.model_name}} -
+
Last Updated on {{disk.smart_results[0]?.date | date:'MMMM dd, yyyy' }}
@@ -89,11 +94,13 @@
S.M.A.R.T
-
{{ disk.smart_results[0]?.smart_status | titlecase}}
+
{{ disk.smart_results[0]?.smart_status | titlecase}}
+
No Data
Temperature
-
{{ disk.smart_results[0]?.temp }}°C
+
{{ disk.smart_results[0]?.temp }}°C
+
--
Capacity
diff --git a/webapp/frontend/src/app/modules/admin/detail/detail.component.html b/webapp/frontend/src/app/modules/admin/detail/detail.component.html index 65f29e2..f4c708b 100644 --- a/webapp/frontend/src/app/modules/admin/detail/detail.component.html +++ b/webapp/frontend/src/app/modules/admin/detail/detail.component.html @@ -83,6 +83,10 @@
{{data.data.rotational_speed}} RPM
Rotation Rate
+
+
{{data.data.device_protocol}}
+
Protocol
+
{{data.data.smart_results[0]?.power_cycle_count}}
Power Cycle Count
@@ -102,8 +106,8 @@
-
S.M.A.R.T Attributes
-
{{this.smartAttributeDataSource.data.length}} visible, {{this.data.data.smart_results[0]?.smart_attributes.length - this.smartAttributeDataSource.data.length}} hidden
+
S.M.A.R.T {{data.data.device_protocol}} Attributes
+
{{this.smartAttributeDataSource.data.length}} visible, {{getHiddenAttributes()}} hidden
@@ -187,8 +191,8 @@ @@ -206,7 +210,7 @@ @@ -224,7 +228,7 @@ @@ -242,7 +246,7 @@ @@ -309,7 +313,7 @@ *matHeaderRowDef="smartAttributeTableColumns"> this.extractAttributeValue(this.data.lookup[attr.attribute_id], hist_attr)).reverse() - rawHistory.push(this.extractAttributeValue(this.data.lookup[attr.attribute_id], attr)) + var rawHistory = (attr.history || []).map(hist_attr => this.getAttributeValue(hist_attr)).reverse() + rawHistory.push(this.getAttributeValue(attr)) attr.chartData = [ { name: "chart-line-sparkline", - // data: Array.from({length: 40}, () => Math.floor(Math.random() * 40)) data: rawHistory } ] - - // //add the reference line showing the threshold - // attr.chartDataReferenceLine = { - // yaxis: [ - // { - // y: attr.thresh, - // borderColor: '#f05252', - // label: { - // borderColor: '#f05252', - // style: { - // color: '#fff', - // background: '#f05252' - // }, - // } - // } - // ] - // } } //determine when to include the attributes in table. - if(!this.onlyCritical || this.onlyCritical && this.data.lookup[attr.attribute_id]?.critical || attr.value <= attr.thresh){ + + if(!this.onlyCritical || this.onlyCritical && this.data.metadata[attr.attribute_id]?.critical || attr.value < attr.thresh){ smartAttributeDataSource.push(attr) } } diff --git a/webapp/frontend/src/app/shared/device-sort.pipe.spec.ts b/webapp/frontend/src/app/shared/device-sort.pipe.spec.ts new file mode 100644 index 0000000..3f9eb0f --- /dev/null +++ b/webapp/frontend/src/app/shared/device-sort.pipe.spec.ts @@ -0,0 +1,8 @@ +import { DeviceSortPipe } from './device-sort.pipe'; + +describe('DeviceSortPipe', () => { + it('create an instance', () => { + const pipe = new DeviceSortPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/webapp/frontend/src/app/shared/device-sort.pipe.ts b/webapp/frontend/src/app/shared/device-sort.pipe.ts new file mode 100644 index 0000000..6f115f1 --- /dev/null +++ b/webapp/frontend/src/app/shared/device-sort.pipe.ts @@ -0,0 +1,33 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'deviceSort' +}) +export class DeviceSortPipe implements PipeTransform { + + numericalStatus(device): number { + if(!device.smart_results[0]){ + return 0 + } else if (device.smart_results[0].smart_status == 'passed'){ + return 1 + } else { + return -1 + } + } + + + transform(devices: Array, ...args: unknown[]): Array { + //failed, unknown/empty, passed + devices.sort((a: any, b: any) => { + + let left = this.numericalStatus(a) + let right = this.numericalStatus(b) + + return left - right; + }); + + + return devices; + } + +} diff --git a/webapp/frontend/src/app/shared/shared.module.ts b/webapp/frontend/src/app/shared/shared.module.ts index ef5abf4..64ef282 100644 --- a/webapp/frontend/src/app/shared/shared.module.ts +++ b/webapp/frontend/src/app/shared/shared.module.ts @@ -2,10 +2,12 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import {FileSizePipe} from "./file-size.pipe"; +import { DeviceSortPipe } from './device-sort.pipe'; @NgModule({ declarations: [ - FileSizePipe + FileSizePipe, + DeviceSortPipe ], imports: [ CommonModule, @@ -16,7 +18,8 @@ import {FileSizePipe} from "./file-size.pipe"; CommonModule, FormsModule, ReactiveFormsModule, - FileSizePipe + FileSizePipe, + DeviceSortPipe ] }) export class SharedModule diff --git a/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark-social.png b/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark-social.png new file mode 100644 index 0000000..7e2474e Binary files /dev/null and b/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark-social.png differ diff --git a/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark-social.psd b/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark-social.psd new file mode 100644 index 0000000..2bddf02 Binary files /dev/null and b/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark-social.psd differ
- - {{attribute.name}} + + {{attribute.name}} - - {{extractAttributeValue(data.lookup[attribute.attribute_id], attribute)}} + + {{getAttributeValue(attribute)}} - {{attribute.worst}} + {{getAttributeWorst(attribute)}} - {{attribute.thresh}} + {{getAttributeThreshold(attribute)}} - {{data.lookup[attribute.attribute_id]?.display_type == "raw" ? data.lookup[attribute.attribute_id]?.ideal : '' }} + {{getAttributeIdeal(attribute) }}