From c913cf39b956c087b6a2da069cd111fab5f54a14 Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Sat, 12 Sep 2020 12:43:41 -0700 Subject: [PATCH 1/5] adding new nottification validation erorr. Added a notification class containing webhook, script and shoutrrr notification logic. Adding "Test notification endpoint". --- example.scrutiny.yaml | 2 +- go.mod | 3 +- go.sum | 57 ++++++++++ webapp/backend/pkg/errors/errors.go | 7 ++ webapp/backend/pkg/notify/notify.go | 161 ++++++++++++++++++++++++++++ webapp/backend/pkg/web/server.go | 10 +- 6 files changed, 233 insertions(+), 7 deletions(-) create mode 100644 webapp/backend/pkg/notify/notify.go diff --git a/example.scrutiny.yaml b/example.scrutiny.yaml index 593d484..17e23e6 100644 --- a/example.scrutiny.yaml +++ b/example.scrutiny.yaml @@ -49,7 +49,7 @@ notify: - "hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz" - "zulip://bot-mail:bot-key@zulip-domain/?stream=name-or-id&topic=name" - "join://shoutrrr:api-key@join/?devices=device1[,device2, ...][&icon=icon][&title=title]" - - "script:///file/path/on/disk" + - "script:///file/path/on/disk" #note the triple slashes "script:///" - "https://www.example.com/path" collect: diff --git a/go.mod b/go.mod index f61b5a6..e9211df 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,15 @@ module github.com/analogj/scrutiny go 1.13 require ( + github.com/AnalogJ/go-util v0.0.0-20200905200945-3b93d31215ae github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14 + github.com/containrrr/shoutrrr v0.0.0-20200828202222-1da53231b05a 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 - github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/sirupsen/logrus v1.2.0 github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.5.1 diff --git a/go.sum b/go.sum index 596660d..47ec7ef 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl 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/AnalogJ/go-util v0.0.0-20200905200945-3b93d31215ae h1:iYSadgTTTmFTvZwdDImnytps8Hq9zlpWeNfYpe1RTPs= +github.com/AnalogJ/go-util v0.0.0-20200905200945-3b93d31215ae/go.mod h1:0jFBtvNk8rNzZjL8j1b852fcka5/VJfJvRiU2w6OIkI= 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= @@ -23,6 +25,7 @@ github.com/analogj/go-util v0.0.0-20190301173314-5295e364eb14 h1:wsrSjiqQtseStRI 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/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 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= @@ -31,13 +34,19 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB 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/containrrr/shoutrrr v0.0.0-20200828202222-1da53231b05a h1:6ZMiughZYF6fJjFIf2X3D7AfImJeXnTMJ9qC2v75WPw= +github.com/containrrr/shoutrrr v0.0.0-20200828202222-1da53231b05a/go.mod h1:z3pUtEhu5zOpu+Q8wZWiEq+ZLL9hM0HiFNhttaI67Ks= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 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/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/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= @@ -48,8 +57,12 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a 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/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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= @@ -98,6 +111,7 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 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.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 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= @@ -123,7 +137,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO 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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= 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= @@ -154,6 +171,7 @@ github.com/kvz/logstreamer v0.0.0-20150507115422-a635b98146f0/go.mod h1:8/LTPeDL 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.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 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= @@ -178,6 +196,8 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu 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/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4= +github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -186,9 +206,15 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9 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/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 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/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= +github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= 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= @@ -220,14 +246,25 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k 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/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 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/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 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/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= 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/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= 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= @@ -240,6 +277,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 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.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 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= @@ -247,6 +285,7 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY 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= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 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= @@ -283,6 +322,7 @@ 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-20180906233101-161cd47e91fd/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= @@ -293,6 +333,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn 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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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= @@ -308,6 +349,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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-20180909124046-d0be0721c37e/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= @@ -319,10 +361,13 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w 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-20191005200804-aed5e4c7ecf9/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/sys v0.0.0-20200409092240-59c9f1ba88fa h1:mQTN3ECqfsViCNBgq+A40vdwhkGykrrQlYe3mPj6BoU= +golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/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= @@ -347,7 +392,10 @@ golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtn 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/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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= @@ -368,14 +416,21 @@ google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBr 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.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= +gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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= @@ -383,6 +438,8 @@ 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= +gosrc.io/xmpp v0.1.1 h1:iMtE9W3fx254+4E6rI34AOPJDqWvpfQR6EYaVMzhJ4s= +gosrc.io/xmpp v0.1.1/go.mod h1:4JgaXzw4MnEv2sGltONtK3GMhj+h9gpQ7cO8nwbFJLU= 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= diff --git a/webapp/backend/pkg/errors/errors.go b/webapp/backend/pkg/errors/errors.go index 87823e0..a26682c 100644 --- a/webapp/backend/pkg/errors/errors.go +++ b/webapp/backend/pkg/errors/errors.go @@ -24,3 +24,10 @@ type DependencyMissingError string func (str DependencyMissingError) Error() string { return fmt.Sprintf("DependencyMissingError: %q", string(str)) } + +// Raised when the notification system is incorrectly configured +type NotificationValidationError string + +func (str NotificationValidationError) Error() string { + return fmt.Sprintf("NotificationValidationError: %q", string(str)) +} diff --git a/webapp/backend/pkg/notify/notify.go b/webapp/backend/pkg/notify/notify.go new file mode 100644 index 0000000..0530474 --- /dev/null +++ b/webapp/backend/pkg/notify/notify.go @@ -0,0 +1,161 @@ +package notify + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/AnalogJ/go-util/utils" + "github.com/analogj/scrutiny/webapp/backend/pkg/config" + "github.com/containrrr/shoutrrr" + log "github.com/sirupsen/logrus" + "net/http" + "os" + "strings" + "sync" + "time" +) + +type Payload struct { + Mailer string `json:"mailer"` + Subject string `json:"subject"` + Date string `json:"date"` + FailureType string `json:"failure_type"` + Device string `json:"device"` + DeviceType string `json:"device_type"` + DeviceString string `json:"device_string"` + Message string `json:"message"` +} + +type Notify struct { + Config config.Interface + Payload Payload +} + +func (n *Notify) Send(level string, payload interface{}) error { + //validate that the Payload is populated + sendDate := time.Now() + n.Payload.Date = sendDate.Format(time.RFC3339) + + //retrieve list of notification endpoints from config file + configUrls := n.Config.GetStringSlice("notify.urls") + + //remove http:// https:// and script:// prefixed urls + notifyWebhooks := []string{} + notifyScripts := []string{} + notifyShoutrrr := []string{} + + for _, url := range configUrls { + if strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") { + notifyWebhooks = append(notifyWebhooks, url) + } else if strings.HasPrefix(url, "script://") { + notifyScripts = append(notifyScripts, url) + } else { + notifyShoutrrr = append(notifyShoutrrr, url) + } + } + + //run all scripts, webhooks and shoutrr commands in parallel + var wg sync.WaitGroup + + for _, notifyWebhook := range notifyWebhooks { + // execute collection in parallel go-routines + wg.Add(1) + go n.SendWebhookNotification(&wg, notifyWebhook) + } + for _, notifyScript := range notifyScripts { + // execute collection in parallel go-routines + wg.Add(1) + go n.SendScriptNotification(&wg, notifyScript) + } + if len(notifyScripts) > 0 { + wg.Add(1) + go n.SendShoutrrrNotification(&wg, notifyShoutrrr) + } + + //and wait for completion, error or timeout. + if waitTimeout(&wg, time.Minute) { //wait for 1 minute + fmt.Println("Timed out while sending notifications") + } else { + fmt.Println("Sent notifications. Check logs for more information.") + } + return nil +} + +func (n *Notify) SendWebhookNotification(wg *sync.WaitGroup, webhookUrl string) { + defer wg.Done() + log.Infof("Sending Webhook to %s", webhookUrl) + requestBody, err := json.Marshal(n.Payload) + if err != nil { + log.Errorf("An error occurred while sending Webhook to %s: %v", webhookUrl, err) + return + } + + resp, err := http.Post(webhookUrl, "application/json", bytes.NewBuffer(requestBody)) + if err != nil { + log.Errorf("An error occurred while sending Webhook to %s: %v", webhookUrl, err) + return + } + defer resp.Body.Close() + //we don't care about resp body content, but maybe we should log it? +} + +func (n *Notify) SendScriptNotification(wg *sync.WaitGroup, scriptUrl string) { + defer wg.Done() + + //check if the script exists. + scriptPath := strings.TrimPrefix(scriptUrl, "script://") + log.Infof("Executing Script %s", scriptPath) + + if !utils.FileExists(scriptPath) { + log.Errorf("Script does not exist: %s", scriptPath) + return + } + + copyEnv := os.Environ() + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_MAILER=%s", n.Payload.Mailer)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_SUBJECT=%s", n.Payload.Subject)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DATE=%s", n.Payload.Date)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_FAILURE_TYPE=%s", n.Payload.FailureType)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE=%s", n.Payload.Device)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE_TYPE=%s", n.Payload.DeviceType)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_DEVICE_STRING=%s", n.Payload.DeviceString)) + copyEnv = append(copyEnv, fmt.Sprintf("SCRUTINY_MESSAGE=%s", n.Payload.Message)) + err := utils.CmdExec(scriptPath, []string{}, "", copyEnv, "") + if err != nil { + log.Errorf("An error occurred while executing script %s: %v", scriptPath, err) + } + return +} + +func (n *Notify) SendShoutrrrNotification(wg *sync.WaitGroup, shoutrrrUrls []string) { + log.Infof("Sending notifications to %v", shoutrrrUrls) + + defer wg.Done() + sender, err := shoutrrr.CreateSender(shoutrrrUrls...) + if err != nil { + log.Errorf("An error occurred while sending notifications %v: %v", shoutrrrUrls, err) + return + } + + errs := sender.Send(n.Payload.Subject, nil) //structs.Map(n.Payload).()) + if len(errs) > 0 { + log.Errorf("One or more errors occurred occurred while sending notifications %v:\n %v", shoutrrrUrls, errs) + } +} + +//utility functions +// waitTimeout waits for the waitgroup for the specified max timeout. +// Returns true if waiting timed out. +func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { + c := make(chan struct{}) + go func() { + defer close(c) + wg.Wait() + }() + select { + case <-c: + return false // completed normally + case <-time.After(timeout): + return true // timed out + } +} diff --git a/webapp/backend/pkg/web/server.go b/webapp/backend/pkg/web/server.go index 9d89dab..156e598 100644 --- a/webapp/backend/pkg/web/server.go +++ b/webapp/backend/pkg/web/server.go @@ -25,13 +25,13 @@ func (ae *AppEngine) Setup() *gin.Engine { "success": true, }) }) + api.POST("/health/notify", handler.SendTestNotification) //check if notifications are configured correctly - api.POST("/devices/register", handler.RegisterDevices) - api.GET("/summary", handler.GetDevicesSummary) - api.POST("/device/:wwn/smart", handler.UploadDeviceMetrics) + api.POST("/devices/register", handler.RegisterDevices) //used by Collector to register new devices and retrieve filtered list + api.GET("/summary", handler.GetDevicesSummary) //used by Dashboard + api.POST("/device/:wwn/smart", handler.UploadDeviceMetrics) //used by Collector to upload data api.POST("/device/:wwn/selftest", handler.UploadDeviceSelfTests) - - api.GET("/device/:wwn/details", handler.GetDeviceDetails) + api.GET("/device/:wwn/details", handler.GetDeviceDetails) //used by Details } //Static request routing From 78a619b09d87e99592823cf0bf81f293c90d0b4d Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Sat, 12 Sep 2020 13:19:08 -0700 Subject: [PATCH 2/5] moved middleware into more relevant location. Adding send test notifications handler. making sure that config is available from web handler functions. --- webapp/backend/pkg/notify/notify.go | 2 +- .../pkg/web/handler/send_test_notification.go | 39 +++++++++++++++++++ webapp/backend/pkg/web/middleware/config.go | 13 +++++++ .../{database => web/middleware}/sqlite3.go | 4 +- webapp/backend/pkg/web/server.go | 5 ++- 5 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 webapp/backend/pkg/web/handler/send_test_notification.go create mode 100644 webapp/backend/pkg/web/middleware/config.go rename webapp/backend/pkg/{database => web/middleware}/sqlite3.go (90%) diff --git a/webapp/backend/pkg/notify/notify.go b/webapp/backend/pkg/notify/notify.go index 0530474..a8f7d98 100644 --- a/webapp/backend/pkg/notify/notify.go +++ b/webapp/backend/pkg/notify/notify.go @@ -31,7 +31,7 @@ type Notify struct { Payload Payload } -func (n *Notify) Send(level string, payload interface{}) error { +func (n *Notify) Send() error { //validate that the Payload is populated sendDate := time.Now() n.Payload.Date = sendDate.Format(time.RFC3339) diff --git a/webapp/backend/pkg/web/handler/send_test_notification.go b/webapp/backend/pkg/web/handler/send_test_notification.go new file mode 100644 index 0000000..5afee0e --- /dev/null +++ b/webapp/backend/pkg/web/handler/send_test_notification.go @@ -0,0 +1,39 @@ +package handler + +import ( + "fmt" + "github.com/analogj/scrutiny/webapp/backend/pkg/config" + dbModels "github.com/analogj/scrutiny/webapp/backend/pkg/models/db" + "github.com/analogj/scrutiny/webapp/backend/pkg/notify" + "github.com/gin-gonic/gin" + "net/http" + "os" +) + +// Send test notification +func SendTestNotification(c *gin.Context) { + appConfig := c.MustGet("CONFIG").(config.Interface) + + testNotify := notify.Notify{ + Config: appConfig, + Payload: notify.Payload{ + Mailer: os.Args[0], + Subject: fmt.Sprintf("Scrutiny SMART error (EmailTest) detected on disk: XXXXX"), + FailureType: "EmailTest", + Device: "/dev/sda", + DeviceType: "ata", + DeviceString: "/dev/sda", + Message: "TEST EMAIL from smartd for device: /dev/sda", + }, + } + err := testNotify.Send() + if err != nil { + c.JSON(http.StatusOK, gin.H{ + "success": false, + }) + } else { + c.JSON(http.StatusOK, dbModels.DeviceWrapper{ + Success: true, + }) + } +} diff --git a/webapp/backend/pkg/web/middleware/config.go b/webapp/backend/pkg/web/middleware/config.go new file mode 100644 index 0000000..026f5aa --- /dev/null +++ b/webapp/backend/pkg/web/middleware/config.go @@ -0,0 +1,13 @@ +package middleware + +import ( + "github.com/analogj/scrutiny/webapp/backend/pkg/config" + "github.com/gin-gonic/gin" +) + +func ConfigMiddleware(appConfig config.Interface) gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("CONFIG", appConfig) + c.Next() + } +} diff --git a/webapp/backend/pkg/database/sqlite3.go b/webapp/backend/pkg/web/middleware/sqlite3.go similarity index 90% rename from webapp/backend/pkg/database/sqlite3.go rename to webapp/backend/pkg/web/middleware/sqlite3.go index 3f4fc45..72792c7 100644 --- a/webapp/backend/pkg/database/sqlite3.go +++ b/webapp/backend/pkg/web/middleware/sqlite3.go @@ -1,4 +1,4 @@ -package database +package middleware import ( "fmt" @@ -8,7 +8,7 @@ import ( _ "github.com/jinzhu/gorm/dialects/sqlite" ) -func DatabaseHandler(dbPath string) gin.HandlerFunc { +func DatabaseMiddleware(dbPath string) gin.HandlerFunc { //var database *gorm.DB fmt.Printf("Trying to connect to database stored: %s", dbPath) database, err := gorm.Open("sqlite3", dbPath) diff --git a/webapp/backend/pkg/web/server.go b/webapp/backend/pkg/web/server.go index 156e598..532b9c5 100644 --- a/webapp/backend/pkg/web/server.go +++ b/webapp/backend/pkg/web/server.go @@ -3,8 +3,8 @@ package web import ( "fmt" "github.com/analogj/scrutiny/webapp/backend/pkg/config" - "github.com/analogj/scrutiny/webapp/backend/pkg/database" "github.com/analogj/scrutiny/webapp/backend/pkg/web/handler" + "github.com/analogj/scrutiny/webapp/backend/pkg/web/middleware" "github.com/gin-gonic/gin" "net/http" ) @@ -16,7 +16,8 @@ type AppEngine struct { func (ae *AppEngine) Setup() *gin.Engine { r := gin.Default() - r.Use(database.DatabaseHandler(ae.Config.GetString("web.database.location"))) + r.Use(middleware.DatabaseMiddleware(ae.Config.GetString("web.database.location"))) + r.Use(middleware.ConfigMiddleware(ae.Config)) api := r.Group("/api") { From 98415e625d51a2eb358b4ac08ad4703c816f8109 Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Sat, 12 Sep 2020 14:40:21 -0700 Subject: [PATCH 3/5] fix import. added simle test for notify test endpoint. --- example.scrutiny.yaml | 2 +- webapp/backend/pkg/notify/notify.go | 2 +- webapp/backend/pkg/web/server_test.go | 23 +++++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/example.scrutiny.yaml b/example.scrutiny.yaml index 17e23e6..593d484 100644 --- a/example.scrutiny.yaml +++ b/example.scrutiny.yaml @@ -49,7 +49,7 @@ notify: - "hangouts://chat.googleapis.com/v1/spaces/FOO/messages?key=bar&token=baz" - "zulip://bot-mail:bot-key@zulip-domain/?stream=name-or-id&topic=name" - "join://shoutrrr:api-key@join/?devices=device1[,device2, ...][&icon=icon][&title=title]" - - "script:///file/path/on/disk" #note the triple slashes "script:///" + - "script:///file/path/on/disk" - "https://www.example.com/path" collect: diff --git a/webapp/backend/pkg/notify/notify.go b/webapp/backend/pkg/notify/notify.go index a8f7d98..e2c4876 100644 --- a/webapp/backend/pkg/notify/notify.go +++ b/webapp/backend/pkg/notify/notify.go @@ -4,7 +4,7 @@ import ( "bytes" "encoding/json" "fmt" - "github.com/AnalogJ/go-util/utils" + "github.com/analogj/go-util/utils" "github.com/analogj/scrutiny/webapp/backend/pkg/config" "github.com/containrrr/shoutrrr" log "github.com/sirupsen/logrus" diff --git a/webapp/backend/pkg/web/server_test.go b/webapp/backend/pkg/web/server_test.go index 1d6d205..785c9ed 100644 --- a/webapp/backend/pkg/web/server_test.go +++ b/webapp/backend/pkg/web/server_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" ) @@ -147,3 +148,25 @@ func TestPopulateMultiple(t *testing.T) { //assert } + +func TestSendTestNotificationRoute(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") + fakeConfig.EXPECT().GetStringSlice("notify.urls").AnyTimes().Return([]string{"https://scrutiny.requestcatcher.com/test"}) + ae := web.AppEngine{ + Config: fakeConfig, + } + router := ae.Setup() + + //test + wr := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/api/health/notify", strings.NewReader("{}")) + router.ServeHTTP(wr, req) + + //assert + require.Equal(t, 200, wr.Code) +} From 5101a3796455c52c9b74c2dacd3d54e8eda4f1cb Mon Sep 17 00:00:00 2001 From: Jason Kulatunga Date: Wed, 16 Sep 2020 08:09:50 -0700 Subject: [PATCH 4/5] adding device protocl and type to the. Adding class for parsing `smartctl --scan` json output, for device detection. added an example/test file for `smartctl -x -j` added a placeholder settings panel. moved dashboard & details compoonent out of "Admin" directory. --- collector/pkg/collector/base.go | 13 +- collector/pkg/models/device.go | 2 + collector/pkg/models/scan.go | 19 + example.scrutiny.yaml | 16 +- webapp/backend/pkg/models/db/device.go | 4 +- .../pkg/models/testdata/smart-ata-full.json | 1708 +++++++++++++++++ webapp/frontend/src/app/app.routing.ts | 4 +- .../dashboard-settings.component.html | 82 + .../dashboard-settings.component.scss | 0 .../dashboard-settings.component.spec.ts | 25 + .../dashboard-settings.component.ts | 17 + .../dashboard-settings.module.ts | 44 + .../dashboard/dashboard.component.html | 9 +- .../dashboard/dashboard.component.scss | 0 .../dashboard/dashboard.component.ts | 15 +- .../{admin => }/dashboard/dashboard.module.ts | 8 +- .../dashboard/dashboard.resolvers.ts | 2 +- .../dashboard/dashboard.routing.ts | 2 +- .../dashboard/dashboard.service.ts | 0 .../{admin => }/detail/detail.component.html | 0 .../{admin => }/detail/detail.component.scss | 0 .../detail/detail.component.spec.ts | 0 .../{admin => }/detail/detail.component.ts | 4 +- .../{admin => }/detail/detail.module.ts | 4 +- .../{admin => }/detail/detail.resolvers.ts | 2 +- .../{admin => }/detail/detail.routing.ts | 2 +- .../{admin => }/detail/detail.service.ts | 0 27 files changed, 1957 insertions(+), 25 deletions(-) create mode 100644 collector/pkg/models/scan.go create mode 100644 webapp/backend/pkg/models/testdata/smart-ata-full.json create mode 100644 webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html create mode 100644 webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.scss create mode 100644 webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.spec.ts create mode 100644 webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts create mode 100644 webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.component.html (96%) rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.component.scss (100%) rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.component.ts (90%) rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.module.ts (77%) rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.resolvers.ts (91%) rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.routing.ts (76%) rename webapp/frontend/src/app/modules/{admin => }/dashboard/dashboard.service.ts (100%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.component.html (100%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.component.scss (100%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.component.spec.ts (100%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.component.ts (98%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.module.ts (88%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.resolvers.ts (92%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.routing.ts (77%) rename webapp/frontend/src/app/modules/{admin => }/detail/detail.service.ts (100%) diff --git a/collector/pkg/collector/base.go b/collector/pkg/collector/base.go index d0768a9..2269dee 100644 --- a/collector/pkg/collector/base.go +++ b/collector/pkg/collector/base.go @@ -23,6 +23,18 @@ type BaseCollector struct { func (c *BaseCollector) DetectStorageDevices() ([]models.Device, error) { + //availableDisksJson, err := c.ExecCmd("smartctl", []string{"-j", "--scan"}, "", os.Environ()) + //if err != nil { + // c.logger.Errorf("Error getting block storage info: %v", err) + // return nil, err + //} + // + //var smartctlScan models.Scan + //err = json.Unmarshal([]byte(availableDisksJson), &smartctlScan) + //if err != nil { + // return nil, err + //} + block, err := ghw.Block() if err != nil { c.logger.Errorf("Error getting block storage info: %v", err) @@ -83,7 +95,6 @@ func (c *BaseCollector) DetectStorageDevices() ([]models.Device, error) { approvedDisks = append(approvedDisks, diskModel) } - return approvedDisks, nil } diff --git a/collector/pkg/models/device.go b/collector/pkg/models/device.go index fcac712..ecfcb06 100644 --- a/collector/pkg/models/device.go +++ b/collector/pkg/models/device.go @@ -14,6 +14,8 @@ type Device struct { Capacity int64 `json:"capacity"` FormFactor string `json:"form_factor"` SmartSupport bool `json:"smart_support"` + DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI) + DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector. } type DeviceWrapper struct { diff --git a/collector/pkg/models/scan.go b/collector/pkg/models/scan.go new file mode 100644 index 0000000..d85ff57 --- /dev/null +++ b/collector/pkg/models/scan.go @@ -0,0 +1,19 @@ +package models + +type Scan struct { + JSONFormatVersion []int `json:"json_format_version"` + Smartctl struct { + Version []int `json:"version"` + SvnRevision string `json:"svn_revision"` + PlatformInfo string `json:"platform_info"` + BuildInfo string `json:"build_info"` + Argv []string `json:"argv"` + ExitStatus int `json:"exit_status"` + } `json:"smartctl"` + Devices []struct { + Name string `json:"name"` + InfoName string `json:"info_name"` + Type string `json:"type"` + Protocol string `json:"protocol"` + } `json:"devices"` +} diff --git a/example.scrutiny.yaml b/example.scrutiny.yaml index 593d484..8872922 100644 --- a/example.scrutiny.yaml +++ b/example.scrutiny.yaml @@ -34,7 +34,6 @@ disks: # - /dev/sdb notify: - level: 'warn' # 'warn' or 'error' urls: - "discord://token@channel" - "telegram://token@telegram?channels=channel-1[,channel-2,...]" @@ -52,6 +51,21 @@ notify: - "script:///file/path/on/disk" - "https://www.example.com/path" +limits: + ata: + critical: + error: 10 + standard: + error: 20 + warn: 10 + scsi: + critical: true + standard: true + nvme: + critical: true + standard: true + + collect: metric: enable: true diff --git a/webapp/backend/pkg/models/db/device.go b/webapp/backend/pkg/models/db/device.go index 1aa147b..8721cea 100644 --- a/webapp/backend/pkg/models/db/device.go +++ b/webapp/backend/pkg/models/db/device.go @@ -34,8 +34,8 @@ type Device struct { 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 + DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI) + DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector. SmartResults []Smart `gorm:"foreignkey:DeviceWWN" json:"smart_results"` } diff --git a/webapp/backend/pkg/models/testdata/smart-ata-full.json b/webapp/backend/pkg/models/testdata/smart-ata-full.json new file mode 100644 index 0000000..82b5250 --- /dev/null +++ b/webapp/backend/pkg/models/testdata/smart-ata-full.json @@ -0,0 +1,1708 @@ +{ + "json_format_version": [ + 1, + 0 + ], + "smartctl": { + "version": [ + 7, + 0 + ], + "svn_revision": "4883", + "platform_info": "x86_64-linux-4.19.143-flatcar", + "build_info": "(local build)", + "argv": [ + "smartctl", + "-x", + "-j", + "/dev/sda" + ], + "exit_status": 0 + }, + "device": { + "name": "/dev/sda", + "info_name": "/dev/sda [SAT]", + "type": "sat", + "protocol": "ATA" + }, + "model_family": "Samsung based SSDs", + "model_name": "Samsung SSD 860 EVO 500GB", + "serial_number": "S3YZNB0KB00864E", + "wwn": { + "naa": 5, + "oui": 9528, + "id": 61213911380 + }, + "firmware_version": "RVT02B6Q", + "user_capacity": { + "blocks": 976773168, + "bytes": 500107862016 + }, + "logical_block_size": 512, + "physical_block_size": 512, + "rotation_rate": 0, + "form_factor": { + "ata_value": 3, + "name": "2.5 inches" + }, + "in_smartctl_database": true, + "ata_version": { + "string": "ACS-4 T13/BSR INCITS 529 revision 5", + "major_value": 2556, + "minor_value": 94 + }, + "sata_version": { + "string": "SATA 3.1", + "value": 127 + }, + "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": 1600014563, + "asctime": "Sun Sep 13 16:29:23 2020 UTC" + }, + "read_lookahead": { + "enabled": true + }, + "write_cache": { + "enabled": true + }, + "ata_security": { + "state": 33, + "string": "Disabled, NOT FROZEN [SEC1]", + "enabled": false, + "frozen": false + }, + "smart_status": { + "passed": true + }, + "ata_smart_data": { + "offline_data_collection": { + "status": { + "value": 128, + "string": "was never started" + }, + "completion_seconds": 0 + }, + "self_test": { + "status": { + "value": 0, + "string": "completed without error", + "passed": true + }, + "polling_minutes": { + "short": 2, + "extended": 85 + } + }, + "capabilities": { + "values": [ + 83, + 3 + ], + "exec_offline_immediate_supported": true, + "offline_is_aborted_upon_new_cmd": false, + "offline_surface_scan_supported": false, + "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": 1, + "table": [ + { + "id": 5, + "name": "Reallocated_Sector_Ct", + "value": 100, + "worst": 100, + "thresh": 10, + "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": 9, + "name": "Power_On_Hours", + "value": 97, + "worst": 97, + "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": 14551, + "string": "14551" + } + }, + { + "id": 12, + "name": "Power_Cycle_Count", + "value": 99, + "worst": 99, + "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": 13, + "string": "13" + } + }, + { + "id": 177, + "name": "Wear_Leveling_Count", + "value": 81, + "worst": 81, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 19, + "string": "PO--C- ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 278, + "string": "278" + } + }, + { + "id": 179, + "name": "Used_Rsvd_Blk_Cnt_Tot", + "value": 100, + "worst": 100, + "thresh": 10, + "when_failed": "", + "flags": { + "value": 19, + "string": "PO--C- ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 181, + "name": "Program_Fail_Cnt_Total", + "value": 100, + "worst": 100, + "thresh": 10, + "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": 182, + "name": "Erase_Fail_Count_Total", + "value": 100, + "worst": 100, + "thresh": 10, + "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": 183, + "name": "Runtime_Bad_Block", + "value": 100, + "worst": 100, + "thresh": 10, + "when_failed": "", + "flags": { + "value": 19, + "string": "PO--C- ", + "prefailure": true, + "updated_online": true, + "performance": false, + "error_rate": false, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 187, + "name": "Uncorrectable_Error_Cnt", + "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": 190, + "name": "Airflow_Temperature_Cel", + "value": 64, + "worst": 43, + "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": 36, + "string": "36" + } + }, + { + "id": 195, + "name": "ECC_Error_Rate", + "value": 200, + "worst": 200, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 26, + "string": "-O-RC- ", + "prefailure": false, + "updated_online": true, + "performance": false, + "error_rate": true, + "event_count": true, + "auto_keep": false + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 199, + "name": "CRC_Error_Count", + "value": 100, + "worst": 100, + "thresh": 0, + "when_failed": "", + "flags": { + "value": 62, + "string": "-OSRCK ", + "prefailure": false, + "updated_online": true, + "performance": true, + "error_rate": true, + "event_count": true, + "auto_keep": true + }, + "raw": { + "value": 0, + "string": "0" + } + }, + { + "id": 235, + "name": "POR_Recovery_Count", + "value": 99, + "worst": 99, + "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": 4, + "string": "4" + } + }, + { + "id": 241, + "name": "Total_LBAs_Written", + "value": 99, + "worst": 99, + "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": 64777770148, + "string": "64777770148" + } + } + ] + }, + "power_on_time": { + "hours": 14551 + }, + "power_cycle_count": 13, + "temperature": { + "current": 36, + "power_cycle_min": 28, + "power_cycle_max": 57, + "lifetime_min": 24, + "lifetime_max": 57, + "op_limit_max": 70, + "op_limit_min": 0, + "limit_min": 0, + "limit_max": 70 + }, + "ata_log_directory": { + "gp_dir_version": 1, + "smart_dir_version": 1, + "smart_dir_multi_sector": true, + "table": [ + { + "address": 0, + "name": "Log Directory", + "read": true, + "write": false, + "gp_sectors": 1, + "smart_sectors": 1 + }, + { + "address": 1, + "name": "Summary SMART error log", + "read": true, + "write": false, + "smart_sectors": 1 + }, + { + "address": 2, + "name": "Comprehensive SMART error log", + "read": true, + "write": false, + "smart_sectors": 1 + }, + { + "address": 3, + "name": "Ext. Comprehensive SMART error log", + "read": true, + "write": false, + "gp_sectors": 1 + }, + { + "address": 4, + "name": "Device Statistics log", + "read": true, + "write": false, + "gp_sectors": 8, + "smart_sectors": 8 + }, + { + "address": 6, + "name": "SMART self-test log", + "read": true, + "write": false, + "smart_sectors": 1 + }, + { + "address": 7, + "name": "Extended self-test log", + "read": true, + "write": false, + "gp_sectors": 1 + }, + { + "address": 9, + "name": "Selective self-test log", + "read": true, + "write": true, + "smart_sectors": 1 + }, + { + "address": 16, + "name": "NCQ Command Error log", + "read": true, + "write": false, + "gp_sectors": 1 + }, + { + "address": 17, + "name": "SATA Phy Event Counters log", + "read": true, + "write": false, + "gp_sectors": 1 + }, + { + "address": 19, + "name": "SATA NCQ Send and Receive log", + "read": true, + "write": false, + "gp_sectors": 1 + }, + { + "address": 48, + "name": "IDENTIFY DEVICE data log", + "read": true, + "write": false, + "gp_sectors": 9, + "smart_sectors": 9 + }, + { + "address": 128, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 129, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 130, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 131, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 132, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 133, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 134, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 135, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 136, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 137, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 138, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 139, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 140, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 141, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 142, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 143, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 144, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 145, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 146, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 147, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 148, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 149, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 150, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 151, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 152, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 153, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 154, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 155, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 156, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 157, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 158, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 159, + "name": "Host vendor specific log", + "read": true, + "write": true, + "gp_sectors": 16, + "smart_sectors": 16 + }, + { + "address": 161, + "name": "Device vendor specific log", + "smart_sectors": 16 + }, + { + "address": 165, + "name": "Device vendor specific log", + "smart_sectors": 16 + }, + { + "address": 206, + "name": "Device vendor specific log", + "smart_sectors": 16 + }, + { + "address": 207, + "name": "Device vendor specific log", + "smart_sectors": 16 + }, + { + "address": 224, + "name": "SCT Command/Status", + "read": true, + "write": true, + "gp_sectors": 1, + "smart_sectors": 1 + }, + { + "address": 225, + "name": "SCT Data Transfer", + "read": true, + "write": true, + "gp_sectors": 1, + "smart_sectors": 1 + } + ] + }, + "ata_smart_error_log": { + "extended": { + "revision": 1, + "sectors": 1, + "count": 0 + } + }, + "ata_smart_self_test_log": { + "extended": { + "revision": 1, + "sectors": 1, + "table": [ + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 14417 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 13985 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12689 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12667 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12665 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12641 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12593 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12569 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12545 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12521 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12499 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12497 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12473 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12449 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12425 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12401 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12377 + }, + { + "type": { + "value": 1, + "string": "Short offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12353 + }, + { + "type": { + "value": 2, + "string": "Extended offline" + }, + "status": { + "value": 0, + "string": "Completed without error", + "passed": true + }, + "lifetime_hours": 12331 + } + ], + "count": 19, + "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 + }, + "ata_sct_status": { + "format_version": 3, + "sct_version": 256, + "device_state": { + "value": 0, + "string": "Active" + }, + "temperature": { + "current": 36, + "power_cycle_min": 28, + "power_cycle_max": 57, + "lifetime_min": 24, + "lifetime_max": 57, + "op_limit_max": 70, + "under_limit_count": 0, + "over_limit_count": 0 + }, + "smart_status": { + "passed": true + } + }, + "ata_sct_temperature_history": { + "version": 2, + "sampling_period_minutes": 1, + "logging_interval_minutes": 10, + "temperature": { + "op_limit_min": 0, + "op_limit_max": 70, + "limit_min": 0, + "limit_max": 70 + }, + "size": 128, + "index": 30, + "table": [ + 39, + 39, + 39, + 39, + 39, + 40, + 40, + 40, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 39, + 38, + 38, + 38, + 39, + 41, + 42, + 42, + 41, + 42, + 43, + 41, + 40, + 40, + 41, + 41, + 41, + 41, + 42, + 41, + 41, + 42, + 41, + 39, + 38, + 37, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 38, + 37, + 37, + 38, + 37, + 37, + 37, + 37, + 36, + 37, + 36, + 36, + 36, + 36, + 36, + 37, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 36, + 38, + 37, + 36, + 37, + 36, + 36, + 37, + 37, + 37, + 37, + 36, + 36, + 37, + 37 + ] + }, + "ata_sct_erc": { + "read": { + "enabled": false + }, + "write": { + "enabled": false + } + }, + "ata_device_statistics": { + "pages": [ + { + "number": 1, + "name": "General Statistics", + "revision": 1, + "table": [ + { + "offset": 8, + "name": "Lifetime Power-On Resets", + "size": 4, + "value": 13, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 16, + "name": "Power-on Hours", + "size": 4, + "value": 14551, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 24, + "name": "Logical Sectors Written", + "size": 6, + "value": 64777770148, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 32, + "name": "Number of Write Commands", + "size": 6, + "value": 1348861990, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 40, + "name": "Logical Sectors Read", + "size": 6, + "value": 34909544344, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 48, + "name": "Number of Read Commands", + "size": 6, + "value": 538928995, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 56, + "name": "Date and Time TimeStamp", + "size": 6, + "value": 1360000, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + } + ] + }, + { + "number": 4, + "name": "General Errors Statistics", + "revision": 1, + "table": [ + { + "offset": 8, + "name": "Number of Reported Uncorrectable Errors", + "size": 4, + "value": 0, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 16, + "name": "Resets Between Cmd Acceptance and Completion", + "size": 4, + "value": 32, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + } + ] + }, + { + "number": 5, + "name": "Temperature Statistics", + "revision": 1, + "table": [ + { + "offset": 8, + "name": "Current Temperature", + "size": 1, + "value": 36, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 32, + "name": "Highest Temperature", + "size": 1, + "value": 57, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 40, + "name": "Lowest Temperature", + "size": 1, + "value": 24, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 88, + "name": "Specified Maximum Operating Temperature", + "size": 1, + "value": 70, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + } + ] + }, + { + "number": 6, + "name": "Transport Statistics", + "revision": 1, + "table": [ + { + "offset": 8, + "name": "Number of Hardware Resets", + "size": 4, + "value": 133, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 16, + "name": "Number of ASR Events", + "size": 4, + "value": 0, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + }, + { + "offset": 24, + "name": "Number of Interface CRC Errors", + "size": 4, + "value": 0, + "flags": { + "value": 192, + "string": "V--- ", + "valid": true, + "normalized": false, + "supports_dsn": false, + "monitored_condition_met": false + } + } + ] + }, + { + "number": 7, + "name": "Solid State Device Statistics", + "revision": 1, + "table": [ + { + "offset": 8, + "name": "Percentage Used Endurance Indicator", + "size": 1, + "value": 19, + "flags": { + "value": 224, + "string": "VN-- ", + "valid": true, + "normalized": true, + "supports_dsn": false, + "monitored_condition_met": false + } + } + ] + } + ] + }, + "sata_phy_event_counters": { + "table": [ + { + "id": 1, + "name": "Command failed due to ICRC error", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 2, + "name": "R_ERR response for data FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 3, + "name": "R_ERR response for device-to-host data FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 4, + "name": "R_ERR response for host-to-device data FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 5, + "name": "R_ERR response for non-data FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 6, + "name": "R_ERR response for device-to-host non-data FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 7, + "name": "R_ERR response for host-to-device non-data FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 8, + "name": "Device-to-host non-data FIS retries", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 9, + "name": "Transition from drive PhyRdy to drive PhyNRdy", + "size": 2, + "value": 8, + "overflow": false + }, + { + "id": 10, + "name": "Device-to-host register FISes sent due to a COMRESET", + "size": 2, + "value": 8, + "overflow": false + }, + { + "id": 11, + "name": "CRC errors within host-to-device FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 13, + "name": "Non-CRC errors within host-to-device FIS", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 15, + "name": "R_ERR response for host-to-device data FIS, CRC", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 16, + "name": "R_ERR response for host-to-device data FIS, non-CRC", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 18, + "name": "R_ERR response for host-to-device non-data FIS, CRC", + "size": 2, + "value": 0, + "overflow": false + }, + { + "id": 19, + "name": "R_ERR response for host-to-device non-data FIS, non-CRC", + "size": 2, + "value": 0, + "overflow": false + } + ], + "reset": false + } +} diff --git a/webapp/frontend/src/app/app.routing.ts b/webapp/frontend/src/app/app.routing.ts index 9ddf5ad..f7ad8bc 100644 --- a/webapp/frontend/src/app/app.routing.ts +++ b/webapp/frontend/src/app/app.routing.ts @@ -26,8 +26,8 @@ export const appRoutes: Route[] = [ children : [ // Example - {path: 'dashboard', loadChildren: () => import('app/modules/admin/dashboard/dashboard.module').then(m => m.DashboardModule)}, - {path: 'device/:wwn', loadChildren: () => import('app/modules/admin/detail/detail.module').then(m => m.DetailModule)} + {path: 'dashboard', loadChildren: () => import('app/modules/dashboard/dashboard.module').then(m => m.DashboardModule)}, + {path: 'device/:wwn', loadChildren: () => import('app/modules/detail/detail.module').then(m => m.DetailModule)} // 404 & Catch all // {path: '404-not-found', pathMatch: 'full', loadChildren: () => import('app/modules/admin/pages/errors/error-404/error-404.module').then(m => m.Error404Module)}, diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html new file mode 100644 index 0000000..d821ecd --- /dev/null +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.html @@ -0,0 +1,82 @@ +

Scrutiny Settings

+ + +
+
+ + Sort By + + Status + Name + Label + + +
+ +
+ + + +
+ + Critical Error Threshold + + + + Critical Warning Threshold + + +
+ +
+ + Error Threshold + + + + Warning Threshold + + +
+ +
+ + +
+ + Critical Error Threshold + + + + Critical Warning Threshold + + +
+ +
+ +
+ + Critical Error Threshold + + + + Critical Warning Threshold + + +
+
+
+
+
+ +
+ + + + + + + diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.scss b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.spec.ts b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.spec.ts new file mode 100644 index 0000000..95a052f --- /dev/null +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DashboardSettingsComponent } from './dashboard-settings.component'; + +describe('DashboardSettingsComponent', () => { + let component: DashboardSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DashboardSettingsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DashboardSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts new file mode 100644 index 0000000..4e1529d --- /dev/null +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-dashboard-settings', + templateUrl: './dashboard-settings.component.html', + styleUrls: ['./dashboard-settings.component.scss'] +}) +export class DashboardSettingsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + formatLabel(value: number) { + return value; + } +} diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts new file mode 100644 index 0000000..7c01da2 --- /dev/null +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts @@ -0,0 +1,44 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { Overlay } from '@angular/cdk/overlay'; +import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { SharedModule } from 'app/shared/shared.module'; +import {DashboardSettingsComponent} from 'app/layout/common/dashboard-settings/dashboard-settings.component' +import { MatDialogModule } from "@angular/material/dialog"; +import { MatButtonToggleModule} from "@angular/material/button-toggle"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatSliderModule} from "@angular/material/slider"; +import {MatSlideToggleModule} from "@angular/material/slide-toggle"; + +@NgModule({ + declarations: [ + DashboardSettingsComponent + ], + imports : [ + RouterModule.forChild([]), + MatAutocompleteModule, + MatDialogModule, + MatButtonModule, + MatSelectModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatButtonToggleModule, + MatTabsModule, + MatSliderModule, + MatSlideToggleModule, + SharedModule + ], + exports : [ + DashboardSettingsComponent + ], + providers : [] +}) +export class DashboardSettingsModule +{ +} diff --git a/webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html b/webapp/frontend/src/app/modules/dashboard/dashboard.component.html similarity index 96% rename from webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html rename to webapp/frontend/src/app/modules/dashboard/dashboard.component.html index 09212ac..af1bc68 100644 --- a/webapp/frontend/src/app/modules/admin/dashboard/dashboard.component.html +++ b/webapp/frontend/src/app/modules/dashboard/dashboard.component.html @@ -10,15 +10,13 @@
- - - + - - - diff --git a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts index 7c01da2..285b4d2 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts +++ b/webapp/frontend/src/app/layout/common/dashboard-settings/dashboard-settings.module.ts @@ -14,6 +14,7 @@ import { MatButtonToggleModule} from "@angular/material/button-toggle"; import {MatTabsModule} from "@angular/material/tabs"; import {MatSliderModule} from "@angular/material/slider"; import {MatSlideToggleModule} from "@angular/material/slide-toggle"; +import {MatTooltipModule} from "@angular/material/tooltip"; @NgModule({ declarations: [ @@ -30,6 +31,7 @@ import {MatSlideToggleModule} from "@angular/material/slide-toggle"; MatInputModule, MatButtonToggleModule, MatTabsModule, + MatTooltipModule, MatSliderModule, MatSlideToggleModule, SharedModule diff --git a/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.html b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.html new file mode 100644 index 0000000..649148d --- /dev/null +++ b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.html @@ -0,0 +1,30 @@ +

Scrutiny Settings

+ + +
+
+ + Threshold Data + + Scrutiny + Manufacturer + + +
+ +
+ + Notifications + + Enabled + Disabled + + +
+
+ +
+ + + + diff --git a/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.scss b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.spec.ts b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.spec.ts new file mode 100644 index 0000000..ce162dc --- /dev/null +++ b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DetailSettingsComponent } from './detail-settings.component'; + +describe('DetailSettingsComponent', () => { + let component: DetailSettingsComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DetailSettingsComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DetailSettingsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.ts b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.ts new file mode 100644 index 0000000..c3542c7 --- /dev/null +++ b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-detail-settings', + templateUrl: './detail-settings.component.html', + styleUrls: ['./detail-settings.component.scss'] +}) +export class DetailSettingsComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.module.ts b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.module.ts new file mode 100644 index 0000000..5569ed0 --- /dev/null +++ b/webapp/frontend/src/app/layout/common/detail-settings/detail-settings.module.ts @@ -0,0 +1,46 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +import { Overlay } from '@angular/cdk/overlay'; +import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MatAutocompleteModule } from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSelectModule } from '@angular/material/select'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { SharedModule } from 'app/shared/shared.module'; +import {DetailSettingsComponent} from 'app/layout/common/detail-settings/detail-settings.component' +import { MatDialogModule } from "@angular/material/dialog"; +import { MatButtonToggleModule} from "@angular/material/button-toggle"; +import {MatTabsModule} from "@angular/material/tabs"; +import {MatSliderModule} from "@angular/material/slider"; +import {MatSlideToggleModule} from "@angular/material/slide-toggle"; +import {MatTooltipModule} from "@angular/material/tooltip"; + +@NgModule({ + declarations: [ + DetailSettingsComponent + ], + imports : [ + RouterModule.forChild([]), + MatAutocompleteModule, + MatDialogModule, + MatButtonModule, + MatSelectModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatButtonToggleModule, + MatTabsModule, + MatTooltipModule, + MatSliderModule, + MatSlideToggleModule, + SharedModule + ], + exports : [ + DetailSettingsComponent + ], + providers : [] +}) +export class DetailSettingsModule +{ +} diff --git a/webapp/frontend/src/app/layout/layout.module.ts b/webapp/frontend/src/app/layout/layout.module.ts index dfd7410..d221c44 100644 --- a/webapp/frontend/src/app/layout/layout.module.ts +++ b/webapp/frontend/src/app/layout/layout.module.ts @@ -16,7 +16,7 @@ const modules = [ @NgModule({ declarations: [ - LayoutComponent + LayoutComponent, ], imports : [ TreoDrawerModule, diff --git a/webapp/frontend/src/app/modules/detail/detail.component.html b/webapp/frontend/src/app/modules/detail/detail.component.html index c0199bf..3f64cdb 100644 --- a/webapp/frontend/src/app/modules/detail/detail.component.html +++ b/webapp/frontend/src/app/modules/detail/detail.component.html @@ -17,7 +17,7 @@ Export