Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a6208c0d49 | |||
| 7840fe66da | |||
| 2ca44c967e | |||
| 4b767421f3 | |||
| 6005b8609a | |||
| df23ecdf33 | |||
| f4988cbac5 | |||
| f4f5d16b4a | |||
| 1c4dd33381 | |||
| 9e0ba4d269 | |||
| d9ecf6c0d3 | |||
| 8051ad4dde | |||
| 165f98dc09 | |||
| ca7772250c | |||
| 6e02e4da02 | |||
| 9c8498cea7 | |||
| 965fbb08da | |||
| e16933eeac | |||
| 4d0fc0eae8 | |||
| 8296a973b8 | |||
| 19a9957755 | |||
| 02e3947906 | |||
| 766a73455e | |||
| 6e64ae09aa | |||
| 411eca20e0 | |||
| 0243d9e2fa | |||
| 9aa0e97be0 | |||
| 488fcfc820 | |||
| b5dad487e5 | |||
| 8b01187892 | |||
| d9d6ce0f30 | |||
| 8d203b3547 | |||
| fe5dbcff1e | |||
| 99df104cdd | |||
| a53397210c | |||
| 2533d8d34f | |||
| af2523cfee | |||
| c6e1663f8a | |||
| ab83c389f7 | |||
| 6d22702864 |
@@ -1,4 +1,3 @@
|
|||||||
/dist
|
|
||||||
/vendor
|
/vendor
|
||||||
/.idea
|
/.idea
|
||||||
/.github
|
/.github
|
||||||
|
|||||||
@@ -22,20 +22,19 @@ See [/docs/TROUBLESHOOTING_DEVICE_COLLECTOR.md](docs/TROUBLESHOOTING_DEVICE_COLL
|
|||||||
|
|
||||||
```
|
```
|
||||||
docker run -it --rm -p 8080:8080 \
|
docker run -it --rm -p 8080:8080 \
|
||||||
|
-v `pwd`/config:/opt/scrutiny/config \
|
||||||
-v /run/udev:/run/udev:ro \
|
-v /run/udev:/run/udev:ro \
|
||||||
--cap-add SYS_RAWIO \
|
--cap-add SYS_RAWIO \
|
||||||
--device=/dev/sda \
|
--device=/dev/sda \
|
||||||
--device=/dev/sdb \
|
--device=/dev/sdb \
|
||||||
-e DEBUG=true \
|
-e DEBUG=true \
|
||||||
-e COLLECTOR_LOG_FILE=/tmp/collector.log \
|
-e COLLECTOR_LOG_FILE=/opt/scrutiny/config/collector.log \
|
||||||
-e SCRUTINY_LOG_FILE=/tmp/web.log \
|
-e SCRUTINY_LOG_FILE=/opt/scrutiny/config/web.log \
|
||||||
--name scrutiny \
|
--name scrutiny \
|
||||||
ghcr.io/analogj/scrutiny:master-omnibus
|
ghcr.io/analogj/scrutiny:master-omnibus
|
||||||
|
|
||||||
# in another terminal trigger the collector
|
# in another terminal trigger the collector
|
||||||
docker exec scrutiny scrutiny-collector-metrics run
|
docker exec scrutiny scrutiny-collector-metrics run
|
||||||
|
|
||||||
# then use docker cp to copy the log files out of the container.
|
|
||||||
docker cp scrutiny:/tmp/collector.log collector.log
|
|
||||||
docker cp scrutiny:/tmp/web.log web.log
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The log files will be available on your host in the `config` directory. Please attach them to this issue.
|
||||||
@@ -25,6 +25,8 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
with:
|
||||||
|
platforms: 'arm64,arm'
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
# Login against a Docker registry except on PR
|
# Login against a Docker registry except on PR
|
||||||
@@ -60,8 +62,8 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
# cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
# cache-to: type=gha,mode=max
|
||||||
|
|
||||||
web:
|
web:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -72,8 +74,22 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
- name: "Populate frontend version information"
|
||||||
|
run: "cd webapp/frontend && ./git.version.sh"
|
||||||
|
- name: "Generate frontend"
|
||||||
|
uses: addnab/docker-run-action@v3
|
||||||
|
with:
|
||||||
|
image: node:lts
|
||||||
|
options: -v ${{ github.workspace }}:/work
|
||||||
|
run: |
|
||||||
|
cd /work
|
||||||
|
make frontend
|
||||||
|
ls -alt /work
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
with:
|
||||||
|
platforms: 'arm64,arm'
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
# Login against a Docker registry except on PR
|
# Login against a Docker registry except on PR
|
||||||
@@ -97,8 +113,6 @@ jobs:
|
|||||||
type=ref,enable=true,event=branch,suffix=-web
|
type=ref,enable=true,event=branch,suffix=-web
|
||||||
type=ref,enable=true,event=tag,suffix=-web
|
type=ref,enable=true,event=tag,suffix=-web
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: "Generate frontend version information"
|
|
||||||
run: "cd webapp/frontend && ./git.version.sh"
|
|
||||||
# Build and push Docker image with Buildx (don't push on PR)
|
# Build and push Docker image with Buildx (don't push on PR)
|
||||||
# https://github.com/docker/build-push-action
|
# https://github.com/docker/build-push-action
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
@@ -110,8 +124,8 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
# cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
# cache-to: type=gha,mode=max
|
||||||
omnibus:
|
omnibus:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
@@ -121,8 +135,22 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
- name: "Populate frontend version information"
|
||||||
|
run: "cd webapp/frontend && ./git.version.sh"
|
||||||
|
- name: "Generate frontend & version information"
|
||||||
|
uses: addnab/docker-run-action@v3
|
||||||
|
with:
|
||||||
|
image: node:lts
|
||||||
|
options: -v ${{ github.workspace }}:/work
|
||||||
|
run: |
|
||||||
|
cd /work
|
||||||
|
make frontend
|
||||||
|
ls -alt /work
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2
|
uses: docker/setup-qemu-action@v2
|
||||||
|
with:
|
||||||
|
platforms: 'arm64,arm'
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2
|
uses: docker/setup-buildx-action@v2
|
||||||
# Login against a Docker registry except on PR
|
# Login against a Docker registry except on PR
|
||||||
@@ -144,8 +172,6 @@ jobs:
|
|||||||
type=ref,enable=true,event=branch,suffix=-omnibus
|
type=ref,enable=true,event=branch,suffix=-omnibus
|
||||||
type=ref,enable=true,event=tag,suffix=-omnibus
|
type=ref,enable=true,event=tag,suffix=-omnibus
|
||||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
- name: "Generate frontend version information"
|
|
||||||
run: "cd webapp/frontend && ./git.version.sh"
|
|
||||||
# Build and push Docker image with Buildx (don't push on PR)
|
# Build and push Docker image with Buildx (don't push on PR)
|
||||||
# https://github.com/docker/build-push-action
|
# https://github.com/docker/build-push-action
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
@@ -157,5 +183,5 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
# cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
# cache-to: type=gha,mode=max
|
||||||
@@ -169,3 +169,19 @@ docker run -it --rm -p 8080:8080 \
|
|||||||
ghcr.io/analogj/scrutiny:master-omnibus
|
ghcr.io/analogj/scrutiny:master-omnibus
|
||||||
/opt/scrutiny/bin/scrutiny-collector-metrics run
|
/opt/scrutiny/bin/scrutiny-collector-metrics run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
# Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 8086:8086 -d --rm \
|
||||||
|
-e DOCKER_INFLUXDB_INIT_MODE=setup \
|
||||||
|
-e DOCKER_INFLUXDB_INIT_USERNAME=admin \
|
||||||
|
-e DOCKER_INFLUXDB_INIT_PASSWORD=password12345 \
|
||||||
|
-e DOCKER_INFLUXDB_INIT_ORG=scrutiny \
|
||||||
|
-e DOCKER_INFLUXDB_INIT_BUCKET=metrics \
|
||||||
|
-e DOCKER_INFLUXDB_INIT_ADMIN_TOKEN=my-super-secret-auth-token \
|
||||||
|
influxdb:2.2
|
||||||
|
go test ./...
|
||||||
|
|
||||||
|
```
|
||||||
@@ -9,6 +9,7 @@ BINARY=\
|
|||||||
linux/arm-7 \
|
linux/arm-7 \
|
||||||
linux/arm64 \
|
linux/arm64 \
|
||||||
|
|
||||||
|
.ONESHELL: # Applies to every targets in the file! .ONESHELL instructs make to invoke a single instance of the shell and provide it with the entire recipe, regardless of how many lines it contains.
|
||||||
.PHONY: all $(BINARY)
|
.PHONY: all $(BINARY)
|
||||||
all: $(BINARY) windows/amd64
|
all: $(BINARY) windows/amd64
|
||||||
|
|
||||||
@@ -38,5 +39,28 @@ windows/amd64:
|
|||||||
@echo "building collector binary (OS = $(OS), ARCH = $(ARCH))"
|
@echo "building collector binary (OS = $(OS), ARCH = $(ARCH))"
|
||||||
xgo -v --targets="$(OS)/$(ARCH)" -ldflags "-extldflags=-static -X main.goos=$(OS) -X main.goarch=$(ARCH)" -out scrutiny-collector-metrics -tags "static netgo" ${GO_WORKSPACE}/collector/cmd/collector-metrics/
|
xgo -v --targets="$(OS)/$(ARCH)" -ldflags "-extldflags=-static -X main.goos=$(OS) -X main.goarch=$(ARCH)" -out scrutiny-collector-metrics -tags "static netgo" ${GO_WORKSPACE}/collector/cmd/collector-metrics/
|
||||||
|
|
||||||
|
|
||||||
|
docker-collector:
|
||||||
|
@echo "building collector docker image"
|
||||||
|
docker build --build-arg TARGETARCH=amd64 -f docker/Dockerfile.collector -t analogj/scrutiny-dev:collector .
|
||||||
|
|
||||||
|
docker-web:
|
||||||
|
@echo "building web docker image"
|
||||||
|
docker build --build-arg TARGETARCH=amd64 -f docker/Dockerfile.web -t analogj/scrutiny-dev:web .
|
||||||
|
|
||||||
|
docker-omnibus:
|
||||||
|
@echo "building omnibus docker image"
|
||||||
|
docker build --build-arg TARGETARCH=amd64 -f docker/Dockerfile -t analogj/scrutiny-dev:omnibus .
|
||||||
|
|
||||||
|
# reduce logging, disable angular-cli analytics for ci environment
|
||||||
|
frontend: export NPM_CONFIG_LOGLEVEL = warn
|
||||||
|
frontend: export NG_CLI_ANALYTICS = false
|
||||||
|
frontend:
|
||||||
|
cd webapp/frontend
|
||||||
|
npm install -g @angular/cli@9.1.4
|
||||||
|
mkdir -p $(CURDIR)/dist
|
||||||
|
npm install
|
||||||
|
npm run build:prod -- --output-path=$(CURDIR)/dist
|
||||||
|
|
||||||
# clean:
|
# clean:
|
||||||
# rm scrutiny-collector-metrics-* scrutiny-web-*
|
# rm scrutiny-collector-metrics-* scrutiny-web-*
|
||||||
|
|||||||
@@ -17,8 +17,6 @@
|
|||||||
WebUI for smartd S.M.A.R.T monitoring
|
WebUI for smartd S.M.A.R.T monitoring
|
||||||
|
|
||||||
> NOTE: Scrutiny is a Work-in-Progress and still has some rough edges.
|
> NOTE: Scrutiny is a Work-in-Progress and still has some rough edges.
|
||||||
>
|
|
||||||
> WARNING: Once the [InfluxDB](https://github.com/AnalogJ/scrutiny/tree/influxdb) branch is merged, Scrutiny will use both sqlite and InfluxDB for data storage. Unfortunately, this may not be backwards compatible with the database structures in the master (sqlite only) branch.
|
|
||||||
|
|
||||||
[](https://imgur.com/a/5k8qMzS)
|
[](https://imgur.com/a/5k8qMzS)
|
||||||
|
|
||||||
@@ -60,11 +58,12 @@ Scrutiny uses `smartctl --scan` to detect devices/drives.
|
|||||||
- All RAID controllers supported by `smartctl` are automatically supported by Scrutiny.
|
- All RAID controllers supported by `smartctl` are automatically supported by Scrutiny.
|
||||||
- While some RAID controllers support passing through the underlying SMART data to `smartctl` others do not.
|
- While some RAID controllers support passing through the underlying SMART data to `smartctl` others do not.
|
||||||
- In some cases `--scan` does not correctly detect the device type, returning [incomplete SMART data](https://github.com/AnalogJ/scrutiny/issues/45).
|
- In some cases `--scan` does not correctly detect the device type, returning [incomplete SMART data](https://github.com/AnalogJ/scrutiny/issues/45).
|
||||||
Scrutiny will eventually support overriding detected device type via the config file.
|
Scrutiny supports overriding detected device type via the config file: see [example.collector.yaml](https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml)
|
||||||
- If you use docker, you **must** pass though the RAID virtual disk to the container using `--device` (see below)
|
- If you use docker, you **must** pass though the RAID virtual disk to the container using `--device` (see below)
|
||||||
- This device may be in `/dev/*` or `/dev/bus/*`.
|
- This device may be in `/dev/*` or `/dev/bus/*`.
|
||||||
- If you're unsure, run `smartctl --scan` on your host, and pass all listed devices to the container.
|
- If you're unsure, run `smartctl --scan` on your host, and pass all listed devices to the container.
|
||||||
|
|
||||||
|
See [docs/TROUBLESHOOTING_DEVICE_COLLECTOR.md](./docs/TROUBLESHOOTING_DEVICE_COLLECTOR.md) for help
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
|
|||||||
@@ -116,12 +116,13 @@ func (mc *MetricsCollector) Collect(deviceWWN string, deviceName string, deviceT
|
|||||||
}
|
}
|
||||||
mc.logger.Infof("Collecting smartctl results for %s\n", deviceName)
|
mc.logger.Infof("Collecting smartctl results for %s\n", deviceName)
|
||||||
|
|
||||||
args := []string{"-x", "-j"}
|
fullDeviceName := fmt.Sprintf("%s%s", detect.DevicePrefix(), deviceName)
|
||||||
|
args := strings.Split(mc.config.GetCommandMetricsSmartArgs(fullDeviceName), " ")
|
||||||
//only include the device type if its a non-standard one. In some cases ata drives are detected as scsi in docker, and metadata is lost.
|
//only include the device type if its a non-standard one. In some cases ata drives are detected as scsi in docker, and metadata is lost.
|
||||||
if len(deviceType) > 0 && deviceType != "scsi" && deviceType != "ata" {
|
if len(deviceType) > 0 && deviceType != "scsi" && deviceType != "ata" {
|
||||||
args = append(args, "-d", deviceType)
|
args = append(args, "--device", deviceType)
|
||||||
}
|
}
|
||||||
args = append(args, fmt.Sprintf("%s%s", detect.DevicePrefix(), deviceName))
|
args = append(args, fullDeviceName)
|
||||||
|
|
||||||
result, err := mc.shell.Command(mc.logger, "smartctl", args, "", os.Environ())
|
result, err := mc.shell.Command(mc.logger, "smartctl", args, "", os.Environ())
|
||||||
resultBytes := []byte(result)
|
resultBytes := []byte(result)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/analogj/go-util/utils"
|
"github.com/analogj/go-util/utils"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/errors"
|
"github.com/analogj/scrutiny/collector/pkg/errors"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/models"
|
"github.com/analogj/scrutiny/collector/pkg/models"
|
||||||
@@ -8,6 +9,8 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// When initializing this class the following methods must be called:
|
// When initializing this class the following methods must be called:
|
||||||
@@ -16,6 +19,8 @@ import (
|
|||||||
// This is done automatically when created via the Factory.
|
// This is done automatically when created via the Factory.
|
||||||
type configuration struct {
|
type configuration struct {
|
||||||
*viper.Viper
|
*viper.Viper
|
||||||
|
|
||||||
|
deviceOverrides []models.ScanOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
//Viper uses the following precedence order. Each item takes precedence over the item below it:
|
//Viper uses the following precedence order. Each item takes precedence over the item below it:
|
||||||
@@ -38,6 +43,10 @@ func (c *configuration) Init() error {
|
|||||||
|
|
||||||
c.SetDefault("api.endpoint", "http://localhost:8080")
|
c.SetDefault("api.endpoint", "http://localhost:8080")
|
||||||
|
|
||||||
|
c.SetDefault("commands.metrics_scan_args", "--scan --json")
|
||||||
|
c.SetDefault("commands.metrics_info_args", "--info --json")
|
||||||
|
c.SetDefault("commands.metrics_smart_args", "--xall --json")
|
||||||
|
|
||||||
//c.SetDefault("collect.short.command", "-a -o on -S on")
|
//c.SetDefault("collect.short.command", "-a -o on -S on")
|
||||||
|
|
||||||
//if you want to load a non-standard location system config file (~/drawbridge.yml), use ReadConfig
|
//if you want to load a non-standard location system config file (~/drawbridge.yml), use ReadConfig
|
||||||
@@ -90,16 +99,89 @@ func (c *configuration) ValidateConfig() error {
|
|||||||
// check that device prefix matches OS
|
// check that device prefix matches OS
|
||||||
// check that schema of config file is valid
|
// check that schema of config file is valid
|
||||||
|
|
||||||
return nil
|
// check that the collector commands are valid
|
||||||
|
commandArgStrings := map[string]string{
|
||||||
|
"commands.metrics_scan_args": c.GetString("commands.metrics_scan_args"),
|
||||||
|
"commands.metrics_info_args": c.GetString("commands.metrics_info_args"),
|
||||||
|
"commands.metrics_smart_args": c.GetString("commands.metrics_smart_args"),
|
||||||
|
}
|
||||||
|
|
||||||
|
errorStrings := []string{}
|
||||||
|
for configKey, commandArgString := range commandArgStrings {
|
||||||
|
args := strings.Split(commandArgString, " ")
|
||||||
|
//ensure that the args string contains `--json` or `-j` flag
|
||||||
|
containsJsonFlag := false
|
||||||
|
containsDeviceFlag := false
|
||||||
|
for _, flag := range args {
|
||||||
|
if strings.HasPrefix(flag, "--json") || strings.HasPrefix(flag, "-j") {
|
||||||
|
containsJsonFlag = true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(flag, "--device") || strings.HasPrefix(flag, "-d") {
|
||||||
|
containsDeviceFlag = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !containsJsonFlag {
|
||||||
|
errorStrings = append(errorStrings, fmt.Sprintf("configuration key '%s' is missing '--json' flag", configKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
if containsDeviceFlag {
|
||||||
|
errorStrings = append(errorStrings, fmt.Sprintf("configuration key '%s' must not contain '--device' or '-d' flag", configKey))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//sort(errorStrings)
|
||||||
|
sort.Strings(errorStrings)
|
||||||
|
|
||||||
|
if len(errorStrings) == 0 {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return errors.ConfigValidationError(strings.Join(errorStrings, ", "))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configuration) GetScanOverrides() []models.ScanOverride {
|
func (c *configuration) GetDeviceOverrides() []models.ScanOverride {
|
||||||
// we have to support 2 types of device types.
|
// we have to support 2 types of device types.
|
||||||
// - simple device type (device_type: 'sat')
|
// - simple device type (device_type: 'sat')
|
||||||
// and list of device types (type: \n- 3ware,0 \n- 3ware,1 \n- 3ware,2)
|
// and list of device types (type: \n- 3ware,0 \n- 3ware,1 \n- 3ware,2)
|
||||||
// GetString will return "" if this is a list of device types.
|
// GetString will return "" if this is a list of device types.
|
||||||
|
|
||||||
overrides := []models.ScanOverride{}
|
if c.deviceOverrides == nil {
|
||||||
c.UnmarshalKey("devices", &overrides, func(c *mapstructure.DecoderConfig) { c.WeaklyTypedInput = true })
|
overrides := []models.ScanOverride{}
|
||||||
return overrides
|
c.UnmarshalKey("devices", &overrides, func(c *mapstructure.DecoderConfig) { c.WeaklyTypedInput = true })
|
||||||
|
c.deviceOverrides = overrides
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.deviceOverrides
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configuration) GetCommandMetricsInfoArgs(deviceName string) string {
|
||||||
|
overrides := c.GetDeviceOverrides()
|
||||||
|
|
||||||
|
for _, deviceOverrides := range overrides {
|
||||||
|
if strings.ToLower(deviceName) == strings.ToLower(deviceOverrides.Device) {
|
||||||
|
//found matching device
|
||||||
|
if len(deviceOverrides.Commands.MetricsInfoArgs) > 0 {
|
||||||
|
return deviceOverrides.Commands.MetricsInfoArgs
|
||||||
|
} else {
|
||||||
|
return c.GetString("commands.metrics_info_args")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.GetString("commands.metrics_info_args")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *configuration) GetCommandMetricsSmartArgs(deviceName string) string {
|
||||||
|
overrides := c.GetDeviceOverrides()
|
||||||
|
|
||||||
|
for _, deviceOverrides := range overrides {
|
||||||
|
if strings.ToLower(deviceName) == strings.ToLower(deviceOverrides.Device) {
|
||||||
|
//found matching device
|
||||||
|
if len(deviceOverrides.Commands.MetricsSmartArgs) > 0 {
|
||||||
|
return deviceOverrides.Commands.MetricsSmartArgs
|
||||||
|
} else {
|
||||||
|
return c.GetString("commands.metrics_smart_args")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.GetString("commands.metrics_smart_args")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func TestConfiguration_GetScanOverrides_Simple(t *testing.T) {
|
|||||||
//test
|
//test
|
||||||
err := testConfig.ReadConfig(path.Join("testdata", "simple_device.yaml"))
|
err := testConfig.ReadConfig(path.Join("testdata", "simple_device.yaml"))
|
||||||
require.NoError(t, err, "should correctly load simple device config")
|
require.NoError(t, err, "should correctly load simple device config")
|
||||||
scanOverrides := testConfig.GetScanOverrides()
|
scanOverrides := testConfig.GetDeviceOverrides()
|
||||||
|
|
||||||
//assert
|
//assert
|
||||||
require.Equal(t, []models.ScanOverride{{Device: "/dev/sda", DeviceType: []string{"sat"}, Ignore: false}}, scanOverrides)
|
require.Equal(t, []models.ScanOverride{{Device: "/dev/sda", DeviceType: []string{"sat"}, Ignore: false}}, scanOverrides)
|
||||||
@@ -45,7 +45,7 @@ func TestConfiguration_GetScanOverrides_Ignore(t *testing.T) {
|
|||||||
//test
|
//test
|
||||||
err := testConfig.ReadConfig(path.Join("testdata", "ignore_device.yaml"))
|
err := testConfig.ReadConfig(path.Join("testdata", "ignore_device.yaml"))
|
||||||
require.NoError(t, err, "should correctly load ignore device config")
|
require.NoError(t, err, "should correctly load ignore device config")
|
||||||
scanOverrides := testConfig.GetScanOverrides()
|
scanOverrides := testConfig.GetDeviceOverrides()
|
||||||
|
|
||||||
//assert
|
//assert
|
||||||
require.Equal(t, []models.ScanOverride{{Device: "/dev/sda", DeviceType: nil, Ignore: true}}, scanOverrides)
|
require.Equal(t, []models.ScanOverride{{Device: "/dev/sda", DeviceType: nil, Ignore: true}}, scanOverrides)
|
||||||
@@ -60,7 +60,7 @@ func TestConfiguration_GetScanOverrides_Raid(t *testing.T) {
|
|||||||
//test
|
//test
|
||||||
err := testConfig.ReadConfig(path.Join("testdata", "raid_device.yaml"))
|
err := testConfig.ReadConfig(path.Join("testdata", "raid_device.yaml"))
|
||||||
require.NoError(t, err, "should correctly load ignore device config")
|
require.NoError(t, err, "should correctly load ignore device config")
|
||||||
scanOverrides := testConfig.GetScanOverrides()
|
scanOverrides := testConfig.GetDeviceOverrides()
|
||||||
|
|
||||||
//assert
|
//assert
|
||||||
require.Equal(t, []models.ScanOverride{
|
require.Equal(t, []models.ScanOverride{
|
||||||
@@ -75,3 +75,53 @@ func TestConfiguration_GetScanOverrides_Raid(t *testing.T) {
|
|||||||
Ignore: false,
|
Ignore: false,
|
||||||
}}, scanOverrides)
|
}}, scanOverrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfiguration_InvalidCommands_MissingJson(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
testConfig, _ := config.Create()
|
||||||
|
|
||||||
|
//test
|
||||||
|
err := testConfig.ReadConfig(path.Join("testdata", "invalid_commands_missing_json.yaml"))
|
||||||
|
require.EqualError(t, err, `ConfigValidationError: "configuration key 'commands.metrics_scan_args' is missing '--json' flag"`, "should throw an error because json flag is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfiguration_InvalidCommands_IncludesDevice(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
testConfig, _ := config.Create()
|
||||||
|
|
||||||
|
//test
|
||||||
|
err := testConfig.ReadConfig(path.Join("testdata", "invalid_commands_includes_device.yaml"))
|
||||||
|
require.EqualError(t, err, `ConfigValidationError: "configuration key 'commands.metrics_info_args' must not contain '--device' or '-d' flag, configuration key 'commands.metrics_smart_args' must not contain '--device' or '-d' flag"`, "should throw an error because device flags detected")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfiguration_OverrideCommands(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
testConfig, _ := config.Create()
|
||||||
|
|
||||||
|
//test
|
||||||
|
err := testConfig.ReadConfig(path.Join("testdata", "override_commands.yaml"))
|
||||||
|
require.NoError(t, err, "should not throw an error")
|
||||||
|
require.Equal(t, "--xall --json -T permissive", testConfig.GetString("commands.metrics_smart_args"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfiguration_OverrideDeviceCommands_MetricsInfoArgs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
testConfig, _ := config.Create()
|
||||||
|
|
||||||
|
//test
|
||||||
|
err := testConfig.ReadConfig(path.Join("testdata", "override_device_commands.yaml"))
|
||||||
|
require.NoError(t, err, "should correctly override device command")
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, "--info --json -T permissive", testConfig.GetCommandMetricsInfoArgs("/dev/sda"))
|
||||||
|
require.Equal(t, "--info --json", testConfig.GetCommandMetricsInfoArgs("/dev/sdb"))
|
||||||
|
//require.Equal(t, []models.ScanOverride{{Device: "/dev/sda", DeviceType: nil, Commands: {MetricsInfoArgs: "--info --json -T "}}}, scanOverrides)
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ type Interface interface {
|
|||||||
GetStringSlice(key string) []string
|
GetStringSlice(key string) []string
|
||||||
UnmarshalKey(key string, rawVal interface{}, decoderOpts ...viper.DecoderConfigOption) error
|
UnmarshalKey(key string, rawVal interface{}, decoderOpts ...viper.DecoderConfigOption) error
|
||||||
|
|
||||||
GetScanOverrides() []models.ScanOverride
|
GetDeviceOverrides() []models.ScanOverride
|
||||||
|
GetCommandMetricsInfoArgs(deviceName string) string
|
||||||
|
GetCommandMetricsSmartArgs(deviceName string) string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,88 +5,37 @@
|
|||||||
package mock_config
|
package mock_config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
reflect "reflect"
|
||||||
|
|
||||||
models "github.com/analogj/scrutiny/collector/pkg/models"
|
models "github.com/analogj/scrutiny/collector/pkg/models"
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
viper "github.com/spf13/viper"
|
viper "github.com/spf13/viper"
|
||||||
reflect "reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockInterface is a mock of Interface interface
|
// MockInterface is a mock of Interface interface.
|
||||||
type MockInterface struct {
|
type MockInterface struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *MockInterfaceMockRecorder
|
recorder *MockInterfaceMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// MockInterfaceMockRecorder is the mock recorder for MockInterface
|
// MockInterfaceMockRecorder is the mock recorder for MockInterface.
|
||||||
type MockInterfaceMockRecorder struct {
|
type MockInterfaceMockRecorder struct {
|
||||||
mock *MockInterface
|
mock *MockInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewMockInterface creates a new mock instance
|
// NewMockInterface creates a new mock instance.
|
||||||
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
|
func NewMockInterface(ctrl *gomock.Controller) *MockInterface {
|
||||||
mock := &MockInterface{ctrl: ctrl}
|
mock := &MockInterface{ctrl: ctrl}
|
||||||
mock.recorder = &MockInterfaceMockRecorder{mock}
|
mock.recorder = &MockInterfaceMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPECT returns an object that allows the caller to indicate expected use
|
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||||
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
|
func (m *MockInterface) EXPECT() *MockInterfaceMockRecorder {
|
||||||
return m.recorder
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Init mocks base method
|
// AllSettings 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{} {
|
func (m *MockInterface) AllSettings() map[string]interface{} {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "AllSettings")
|
ret := m.ctrl.Call(m, "AllSettings")
|
||||||
@@ -94,27 +43,13 @@ func (m *MockInterface) AllSettings() map[string]interface{} {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllSettings indicates an expected call of AllSettings
|
// AllSettings indicates an expected call of AllSettings.
|
||||||
func (mr *MockInterfaceMockRecorder) AllSettings() *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) AllSettings() *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllSettings", reflect.TypeOf((*MockInterface)(nil).AllSettings))
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllSettings", reflect.TypeOf((*MockInterface)(nil).AllSettings))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsSet mocks base method
|
// Get 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{} {
|
func (m *MockInterface) Get(key string) interface{} {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "Get", key)
|
ret := m.ctrl.Call(m, "Get", key)
|
||||||
@@ -122,13 +57,13 @@ func (m *MockInterface) Get(key string) interface{} {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get indicates an expected call of Get
|
// Get indicates an expected call of Get.
|
||||||
func (mr *MockInterfaceMockRecorder) Get(key interface{}) *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) Get(key interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), key)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockInterface)(nil).Get), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBool mocks base method
|
// GetBool mocks base method.
|
||||||
func (m *MockInterface) GetBool(key string) bool {
|
func (m *MockInterface) GetBool(key string) bool {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetBool", key)
|
ret := m.ctrl.Call(m, "GetBool", key)
|
||||||
@@ -136,13 +71,55 @@ func (m *MockInterface) GetBool(key string) bool {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBool indicates an expected call of GetBool
|
// GetBool indicates an expected call of GetBool.
|
||||||
func (mr *MockInterfaceMockRecorder) GetBool(key interface{}) *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) GetBool(key interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockInterface)(nil).GetBool), key)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBool", reflect.TypeOf((*MockInterface)(nil).GetBool), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt mocks base method
|
// GetCommandMetricsInfoArgs mocks base method.
|
||||||
|
func (m *MockInterface) GetCommandMetricsInfoArgs(deviceName string) string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetCommandMetricsInfoArgs", deviceName)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommandMetricsInfoArgs indicates an expected call of GetCommandMetricsInfoArgs.
|
||||||
|
func (mr *MockInterfaceMockRecorder) GetCommandMetricsInfoArgs(deviceName interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommandMetricsInfoArgs", reflect.TypeOf((*MockInterface)(nil).GetCommandMetricsInfoArgs), deviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommandMetricsSmartArgs mocks base method.
|
||||||
|
func (m *MockInterface) GetCommandMetricsSmartArgs(deviceName string) string {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetCommandMetricsSmartArgs", deviceName)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommandMetricsSmartArgs indicates an expected call of GetCommandMetricsSmartArgs.
|
||||||
|
func (mr *MockInterfaceMockRecorder) GetCommandMetricsSmartArgs(deviceName interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCommandMetricsSmartArgs", reflect.TypeOf((*MockInterface)(nil).GetCommandMetricsSmartArgs), deviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceOverrides mocks base method.
|
||||||
|
func (m *MockInterface) GetDeviceOverrides() []models.ScanOverride {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetDeviceOverrides")
|
||||||
|
ret0, _ := ret[0].([]models.ScanOverride)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDeviceOverrides indicates an expected call of GetDeviceOverrides.
|
||||||
|
func (mr *MockInterfaceMockRecorder) GetDeviceOverrides() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDeviceOverrides", reflect.TypeOf((*MockInterface)(nil).GetDeviceOverrides))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt mocks base method.
|
||||||
func (m *MockInterface) GetInt(key string) int {
|
func (m *MockInterface) GetInt(key string) int {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetInt", key)
|
ret := m.ctrl.Call(m, "GetInt", key)
|
||||||
@@ -150,13 +127,13 @@ func (m *MockInterface) GetInt(key string) int {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt indicates an expected call of GetInt
|
// GetInt indicates an expected call of GetInt.
|
||||||
func (mr *MockInterfaceMockRecorder) GetInt(key interface{}) *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) GetInt(key interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockInterface)(nil).GetInt), key)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInt", reflect.TypeOf((*MockInterface)(nil).GetInt), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString mocks base method
|
// GetString mocks base method.
|
||||||
func (m *MockInterface) GetString(key string) string {
|
func (m *MockInterface) GetString(key string) string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetString", key)
|
ret := m.ctrl.Call(m, "GetString", key)
|
||||||
@@ -164,13 +141,13 @@ func (m *MockInterface) GetString(key string) string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetString indicates an expected call of GetString
|
// GetString indicates an expected call of GetString.
|
||||||
func (mr *MockInterfaceMockRecorder) GetString(key interface{}) *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) GetString(key interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockInterface)(nil).GetString), key)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetString", reflect.TypeOf((*MockInterface)(nil).GetString), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringSlice mocks base method
|
// GetStringSlice mocks base method.
|
||||||
func (m *MockInterface) GetStringSlice(key string) []string {
|
func (m *MockInterface) GetStringSlice(key string) []string {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "GetStringSlice", key)
|
ret := m.ctrl.Call(m, "GetStringSlice", key)
|
||||||
@@ -178,13 +155,79 @@ func (m *MockInterface) GetStringSlice(key string) []string {
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringSlice indicates an expected call of GetStringSlice
|
// GetStringSlice indicates an expected call of GetStringSlice.
|
||||||
func (mr *MockInterfaceMockRecorder) GetStringSlice(key interface{}) *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) GetStringSlice(key interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStringSlice", reflect.TypeOf((*MockInterface)(nil).GetStringSlice), key)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStringSlice", reflect.TypeOf((*MockInterface)(nil).GetStringSlice), key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalKey mocks base method
|
// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalKey mocks base method.
|
||||||
func (m *MockInterface) UnmarshalKey(key string, rawVal interface{}, decoderOpts ...viper.DecoderConfigOption) error {
|
func (m *MockInterface) UnmarshalKey(key string, rawVal interface{}, decoderOpts ...viper.DecoderConfigOption) error {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []interface{}{key, rawVal}
|
varargs := []interface{}{key, rawVal}
|
||||||
@@ -196,23 +239,9 @@ func (m *MockInterface) UnmarshalKey(key string, rawVal interface{}, decoderOpts
|
|||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalKey indicates an expected call of UnmarshalKey
|
// UnmarshalKey indicates an expected call of UnmarshalKey.
|
||||||
func (mr *MockInterfaceMockRecorder) UnmarshalKey(key, rawVal interface{}, decoderOpts ...interface{}) *gomock.Call {
|
func (mr *MockInterfaceMockRecorder) UnmarshalKey(key, rawVal interface{}, decoderOpts ...interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
varargs := append([]interface{}{key, rawVal}, decoderOpts...)
|
varargs := append([]interface{}{key, rawVal}, decoderOpts...)
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalKey", reflect.TypeOf((*MockInterface)(nil).UnmarshalKey), varargs...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnmarshalKey", reflect.TypeOf((*MockInterface)(nil).UnmarshalKey), varargs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetScanOverrides mocks base method
|
|
||||||
func (m *MockInterface) GetScanOverrides() []models.ScanOverride {
|
|
||||||
m.ctrl.T.Helper()
|
|
||||||
ret := m.ctrl.Call(m, "GetScanOverrides")
|
|
||||||
ret0, _ := ret[0].([]models.ScanOverride)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetScanOverrides indicates an expected call of GetScanOverrides
|
|
||||||
func (mr *MockInterfaceMockRecorder) GetScanOverrides() *gomock.Call {
|
|
||||||
mr.mock.ctrl.T.Helper()
|
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetScanOverrides", reflect.TypeOf((*MockInterface)(nil).GetScanOverrides))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
commands:
|
||||||
|
metrics_scan_args: '--scan --json' # used to detect devices
|
||||||
|
metrics_info_args: '--info --json --device=sat' # used to determine device unique ID & register device with Scrutiny
|
||||||
|
metrics_smart_args: '--xall --json -d sat' # used to retrieve smart data for each device.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
commands:
|
||||||
|
metrics_scan_args: '--scan' # used to detect devices
|
||||||
|
metrics_info_args: '--info -j' # used to determine device unique ID & register device with Scrutiny
|
||||||
|
metrics_smart_args: '--xall --json' # used to retrieve smart data for each device.
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
commands:
|
||||||
|
metrics_scan_args: '--scan --json' # used to detect devices
|
||||||
|
metrics_info_args: '--info -j' # used to determine device unique ID & register device with Scrutiny
|
||||||
|
metrics_smart_args: '--xall --json -T permissive' # used to retrieve smart data for each device.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
version: 1
|
||||||
|
devices:
|
||||||
|
- device: /dev/sda
|
||||||
|
commands:
|
||||||
|
metrics_info_args: "--info --json -T permissive"
|
||||||
@@ -28,7 +28,8 @@ type Detect struct {
|
|||||||
// models.Device returned from this function only contain the minimum data for smartctl to execute: device type and device name (device file).
|
// models.Device returned from this function only contain the minimum data for smartctl to execute: device type and device name (device file).
|
||||||
func (d *Detect) SmartctlScan() ([]models.Device, error) {
|
func (d *Detect) SmartctlScan() ([]models.Device, error) {
|
||||||
//we use smartctl to detect all the drives available.
|
//we use smartctl to detect all the drives available.
|
||||||
detectedDeviceConnJson, err := d.Shell.Command(d.Logger, "smartctl", []string{"--scan", "-j"}, "", os.Environ())
|
args := strings.Split(d.Config.GetString("commands.metrics_scan_args"), " ")
|
||||||
|
detectedDeviceConnJson, err := d.Shell.Command(d.Logger, "smartctl", args, "", os.Environ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.Logger.Errorf("Error scanning for devices: %v", err)
|
d.Logger.Errorf("Error scanning for devices: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -51,13 +52,13 @@ func (d *Detect) SmartctlScan() ([]models.Device, error) {
|
|||||||
// - WWN is provided as component data, rather than a "string". We'll have to generate the WWN value ourselves
|
// - WWN is provided as component data, rather than a "string". We'll have to generate the WWN value ourselves
|
||||||
// - WWN from smartctl only provided for ATA protocol drives, NVMe and SCSI drives do not include WWN.
|
// - WWN from smartctl only provided for ATA protocol drives, NVMe and SCSI drives do not include WWN.
|
||||||
func (d *Detect) SmartCtlInfo(device *models.Device) error {
|
func (d *Detect) SmartCtlInfo(device *models.Device) error {
|
||||||
|
fullDeviceName := fmt.Sprintf("%s%s", DevicePrefix(), device.DeviceName)
|
||||||
args := []string{"--info", "-j"}
|
args := strings.Split(d.Config.GetCommandMetricsInfoArgs(fullDeviceName), " ")
|
||||||
//only include the device type if its a non-standard one. In some cases ata drives are detected as scsi in docker, and metadata is lost.
|
//only include the device type if its a non-standard one. In some cases ata drives are detected as scsi in docker, and metadata is lost.
|
||||||
if len(device.DeviceType) > 0 && device.DeviceType != "scsi" && device.DeviceType != "ata" {
|
if len(device.DeviceType) > 0 && device.DeviceType != "scsi" && device.DeviceType != "ata" {
|
||||||
args = append(args, "-d", device.DeviceType)
|
args = append(args, "--device", device.DeviceType)
|
||||||
}
|
}
|
||||||
args = append(args, fmt.Sprintf("%s%s", DevicePrefix(), device.DeviceName))
|
args = append(args, fullDeviceName)
|
||||||
|
|
||||||
availableDeviceInfoJson, err := d.Shell.Command(d.Logger, "smartctl", args, "", os.Environ())
|
availableDeviceInfoJson, err := d.Shell.Command(d.Logger, "smartctl", args, "", os.Environ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -138,7 +139,7 @@ func (d *Detect) TransformDetectedDevices(detectedDeviceConns models.Scan) []mod
|
|||||||
|
|
||||||
//now tha we've "grouped" all the devices, lets override any groups specified in the config file.
|
//now tha we've "grouped" all the devices, lets override any groups specified in the config file.
|
||||||
|
|
||||||
for _, overrideDevice := range d.Config.GetScanOverrides() {
|
for _, overrideDevice := range d.Config.GetDeviceOverrides() {
|
||||||
overrideDeviceFile := strings.ToLower(overrideDevice.Device)
|
overrideDeviceFile := strings.ToLower(overrideDevice.Device)
|
||||||
|
|
||||||
if overrideDevice.Ignore {
|
if overrideDevice.Ignore {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ func TestDetect_SmartctlScan(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{})
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{})
|
||||||
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
||||||
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_simple.json")
|
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_simple.json")
|
||||||
@@ -45,7 +46,8 @@ func TestDetect_SmartctlScan_Megaraid(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{})
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{})
|
||||||
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
||||||
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_megaraid.json")
|
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_megaraid.json")
|
||||||
@@ -75,7 +77,8 @@ func TestDetect_SmartctlScan_Nvme(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{})
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{})
|
||||||
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
fakeShell := mock_shell.NewMockInterface(mockCtrl)
|
||||||
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_nvme.json")
|
testScanResults, err := ioutil.ReadFile("testdata/smartctl_scan_nvme.json")
|
||||||
@@ -104,7 +107,9 @@ func TestDetect_TransformDetectedDevices_Empty(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{})
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{})
|
||||||
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
detectedDevices := models.Scan{
|
detectedDevices := models.Scan{
|
||||||
Devices: []models.ScanDevice{
|
Devices: []models.ScanDevice{
|
||||||
{
|
{
|
||||||
@@ -134,7 +139,9 @@ func TestDetect_TransformDetectedDevices_Ignore(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{{Device: "/dev/sda", DeviceType: nil, Ignore: true}})
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{{Device: "/dev/sda", DeviceType: nil, Ignore: true}})
|
||||||
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
|
||||||
detectedDevices := models.Scan{
|
detectedDevices := models.Scan{
|
||||||
Devices: []models.ScanDevice{
|
Devices: []models.ScanDevice{
|
||||||
{
|
{
|
||||||
@@ -163,7 +170,8 @@ func TestDetect_TransformDetectedDevices_Raid(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{
|
||||||
{
|
{
|
||||||
Device: "/dev/bus/0",
|
Device: "/dev/bus/0",
|
||||||
DeviceType: []string{"megaraid,14", "megaraid,15", "megaraid,18", "megaraid,19", "megaraid,20", "megaraid,21"},
|
DeviceType: []string{"megaraid,14", "megaraid,15", "megaraid,18", "megaraid,19", "megaraid,20", "megaraid,21"},
|
||||||
@@ -202,7 +210,8 @@ func TestDetect_TransformDetectedDevices_Simple(t *testing.T) {
|
|||||||
defer mockCtrl.Finish()
|
defer mockCtrl.Finish()
|
||||||
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
fakeConfig.EXPECT().GetString("host.id").AnyTimes().Return("")
|
||||||
fakeConfig.EXPECT().GetScanOverrides().AnyTimes().Return([]models.ScanOverride{{Device: "/dev/sda", DeviceType: []string{"sat+megaraid"}}})
|
fakeConfig.EXPECT().GetString("commands.metrics_scan_args").AnyTimes().Return("--scan --json")
|
||||||
|
fakeConfig.EXPECT().GetDeviceOverrides().AnyTimes().Return([]models.ScanOverride{{Device: "/dev/sda", DeviceType: []string{"sat+megaraid"}}})
|
||||||
detectedDevices := models.Scan{
|
detectedDevices := models.Scan{
|
||||||
Devices: []models.ScanDevice{
|
Devices: []models.ScanDevice{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,4 +4,8 @@ type ScanOverride struct {
|
|||||||
Device string `mapstructure:"device"`
|
Device string `mapstructure:"device"`
|
||||||
DeviceType []string `mapstructure:"type"`
|
DeviceType []string `mapstructure:"type"`
|
||||||
Ignore bool `mapstructure:"ignore"`
|
Ignore bool `mapstructure:"ignore"`
|
||||||
|
Commands struct {
|
||||||
|
MetricsInfoArgs string `mapstructure:"metrics_info_args"`
|
||||||
|
MetricsSmartArgs string `mapstructure:"metrics_smart_args"`
|
||||||
|
} `mapstructure:"commands"`
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-22
@@ -11,39 +11,25 @@ RUN go mod vendor && \
|
|||||||
go build -ldflags '-w -extldflags "-static"' -o scrutiny-collector-metrics collector/cmd/collector-metrics/collector-metrics.go
|
go build -ldflags '-w -extldflags "-static"' -o scrutiny-collector-metrics collector/cmd/collector-metrics/collector-metrics.go
|
||||||
|
|
||||||
########
|
########
|
||||||
FROM node:lts-slim as frontendbuild
|
FROM ubuntu:latest as runtime
|
||||||
|
|
||||||
#reduce logging, disable angular-cli analytics for ci environment
|
|
||||||
ENV NPM_CONFIG_LOGLEVEL=warn NG_CLI_ANALYTICS=false
|
|
||||||
|
|
||||||
WORKDIR /opt/scrutiny/src
|
|
||||||
COPY webapp/frontend /opt/scrutiny/src
|
|
||||||
|
|
||||||
RUN npm install -g @angular/cli@9.1.4 && \
|
|
||||||
mkdir -p /scrutiny/dist && \
|
|
||||||
npm install && \
|
|
||||||
npm run build:prod -- --output-path=/opt/scrutiny/dist
|
|
||||||
|
|
||||||
|
|
||||||
########
|
|
||||||
FROM ubuntu:bionic as runtime
|
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
WORKDIR /opt/scrutiny
|
WORKDIR /opt/scrutiny
|
||||||
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||||
ENV INFLUXD_CONFIG_PATH=/opt/scrutiny/influxdb
|
ENV INFLUXD_CONFIG_PATH=/opt/scrutiny/influxdb
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y cron smartmontools=7.0-0ubuntu1~ubuntu18.04.1 ca-certificates curl tzdata \
|
RUN apt-get update && apt-get install -y cron smartmontools ca-certificates curl tzdata \
|
||||||
&& update-ca-certificates \
|
&& update-ca-certificates \
|
||||||
&& case ${TARGETARCH} in \
|
&& case ${TARGETARCH} in \
|
||||||
"amd64") S6_ARCH=amd64 ;; \
|
"amd64") S6_ARCH=amd64 ;; \
|
||||||
"arm64") S6_ARCH=aarch64 ;; \
|
"arm64") S6_ARCH=aarch64 ;; \
|
||||||
esac \
|
esac \
|
||||||
&& curl https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \
|
&& curl https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \
|
||||||
&& tar xzf /tmp/s6-overlay-${S6_ARCH}.tar.gz -C /
|
&& tar xzf /tmp/s6-overlay-${S6_ARCH}.tar.gz -C / --exclude="./bin" \
|
||||||
|
&& tar xzf /tmp/s6-overlay-${S6_ARCH}.tar.gz -C /usr ./bin \
|
||||||
ADD https://dl.influxdata.com/influxdb/releases/influxdb2-2.2.0-${TARGETARCH}.deb /tmp/
|
&& rm -rf /tmp/s6-overlay-${S6_ARCH}.tar.gz \
|
||||||
RUN dpkg -i /tmp/influxdb2-2.2.0-${TARGETARCH}.deb && rm -rf /tmp/influxdb2-2.2.0-${TARGETARCH}.deb
|
&& curl -L https://dl.influxdata.com/influxdb/releases/influxdb2-2.2.0-${TARGETARCH}.deb --output /tmp/influxdb2-2.2.0-${TARGETARCH}.deb \
|
||||||
|
&& dpkg -i --force-all /tmp/influxdb2-2.2.0-${TARGETARCH}.deb
|
||||||
|
|
||||||
COPY /rootfs /
|
COPY /rootfs /
|
||||||
|
|
||||||
@@ -51,7 +37,7 @@ COPY /rootfs/etc/cron.d/scrutiny /etc/cron.d/scrutiny
|
|||||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny-collector-selftest /opt/scrutiny/bin/
|
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny-collector-selftest /opt/scrutiny/bin/
|
||||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny-collector-metrics /opt/scrutiny/bin/
|
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny-collector-metrics /opt/scrutiny/bin/
|
||||||
COPY --from=frontendbuild /opt/scrutiny/dist /opt/scrutiny/web
|
COPY dist /opt/scrutiny/web
|
||||||
RUN chmod +x /opt/scrutiny/bin/scrutiny && \
|
RUN chmod +x /opt/scrutiny/bin/scrutiny && \
|
||||||
chmod +x /opt/scrutiny/bin/scrutiny-collector-selftest && \
|
chmod +x /opt/scrutiny/bin/scrutiny-collector-selftest && \
|
||||||
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics && \
|
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics && \
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ RUN go mod vendor && \
|
|||||||
go build -ldflags '-w -extldflags "-static"' -o scrutiny-collector-metrics collector/cmd/collector-metrics/collector-metrics.go
|
go build -ldflags '-w -extldflags "-static"' -o scrutiny-collector-metrics collector/cmd/collector-metrics/collector-metrics.go
|
||||||
|
|
||||||
########
|
########
|
||||||
FROM ubuntu:bionic as runtime
|
FROM ubuntu:latest as runtime
|
||||||
WORKDIR /scrutiny
|
WORKDIR /scrutiny
|
||||||
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y cron smartmontools=7.0-0ubuntu1~ubuntu18.04.1 ca-certificates tzdata && update-ca-certificates
|
RUN apt-get update && apt-get install -y cron smartmontools ca-certificates tzdata && update-ca-certificates
|
||||||
|
|
||||||
COPY /docker/entrypoint-collector.sh /entrypoint-collector.sh
|
COPY /docker/entrypoint-collector.sh /entrypoint-collector.sh
|
||||||
COPY /rootfs/etc/cron.d/scrutiny /etc/cron.d/scrutiny
|
COPY /rootfs/etc/cron.d/scrutiny /etc/cron.d/scrutiny
|
||||||
|
|||||||
+2
-17
@@ -9,22 +9,7 @@ RUN go mod vendor && \
|
|||||||
go build -ldflags '-w -extldflags "-static"' -o scrutiny webapp/backend/cmd/scrutiny/scrutiny.go
|
go build -ldflags '-w -extldflags "-static"' -o scrutiny webapp/backend/cmd/scrutiny/scrutiny.go
|
||||||
|
|
||||||
########
|
########
|
||||||
FROM node:lts-slim as frontendbuild
|
FROM ubuntu:latest as runtime
|
||||||
|
|
||||||
#reduce logging, disable angular-cli analytics for ci environment
|
|
||||||
ENV NPM_CONFIG_LOGLEVEL=warn NG_CLI_ANALYTICS=false
|
|
||||||
|
|
||||||
WORKDIR /opt/scrutiny/src
|
|
||||||
COPY webapp/frontend /opt/scrutiny/src
|
|
||||||
|
|
||||||
RUN npm install -g @angular/cli@9.1.4 && \
|
|
||||||
mkdir -p /opt/scrutiny/dist && \
|
|
||||||
npm install && \
|
|
||||||
npm run build:prod -- --output-path=/opt/scrutiny/dist
|
|
||||||
|
|
||||||
|
|
||||||
########
|
|
||||||
FROM ubuntu:bionic as runtime
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
WORKDIR /opt/scrutiny
|
WORKDIR /opt/scrutiny
|
||||||
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
ENV PATH="/opt/scrutiny/bin:${PATH}"
|
||||||
@@ -32,7 +17,7 @@ ENV PATH="/opt/scrutiny/bin:${PATH}"
|
|||||||
RUN apt-get update && apt-get install -y ca-certificates curl tzdata && update-ca-certificates
|
RUN apt-get update && apt-get install -y ca-certificates curl tzdata && update-ca-certificates
|
||||||
|
|
||||||
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
COPY --from=backendbuild /go/src/github.com/analogj/scrutiny/scrutiny /opt/scrutiny/bin/
|
||||||
COPY --from=frontendbuild /opt/scrutiny/dist /opt/scrutiny/web
|
COPY dist /opt/scrutiny/web
|
||||||
RUN chmod +x /opt/scrutiny/bin/scrutiny && \
|
RUN chmod +x /opt/scrutiny/bin/scrutiny && \
|
||||||
mkdir -p /opt/scrutiny/web && \
|
mkdir -p /opt/scrutiny/web && \
|
||||||
mkdir -p /opt/scrutiny/config && \
|
mkdir -p /opt/scrutiny/config && \
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
> See [docker/example.hubspoke.docker-compose.yml](./docker/example.hubspoke.docker-compose.yml) for a docker-compose file.
|
> See [docker/example.hubspoke.docker-compose.yml](https://github.com/AnalogJ/scrutiny/blob/master/docker/example.hubspoke.docker-compose.yml) for a docker-compose file.
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ web:
|
|||||||
# and store the information in the config file. If you 're re-using an existing influxdb installation, you'll need to provide
|
# and store the information in the config file. If you 're re-using an existing influxdb installation, you'll need to provide
|
||||||
# the `token`
|
# the `token`
|
||||||
influxdb:
|
influxdb:
|
||||||
host: 0.0.0.0
|
host: localhost
|
||||||
port: 8086
|
port: 8086
|
||||||
# token: 'my-token'
|
# token: 'my-token'
|
||||||
# org: 'my-org'
|
# org: 'my-org'
|
||||||
@@ -83,9 +83,11 @@ Now that we have downloaded the required files, let's prepare the filesystem.
|
|||||||
chmod +x /opt/scrutiny/bin/scrutiny-web-linux-amd64
|
chmod +x /opt/scrutiny/bin/scrutiny-web-linux-amd64
|
||||||
|
|
||||||
# Next, lets extract the frontend files.
|
# Next, lets extract the frontend files.
|
||||||
|
# NOTE: after extraction, there **should not** be a `dist` subdirectory in `/opt/scrutiny/web` directory.
|
||||||
cd /opt/scrutiny/web
|
cd /opt/scrutiny/web
|
||||||
tar xvzf scrutiny-web-frontend.tar.gz --strip-components 1 -C .
|
tar xvzf scrutiny-web-frontend.tar.gz --strip-components 1 -C .
|
||||||
|
|
||||||
|
|
||||||
# Cleanup
|
# Cleanup
|
||||||
rm -rf scrutiny-web-frontend.tar.gz
|
rm -rf scrutiny-web-frontend.tar.gz
|
||||||
```
|
```
|
||||||
@@ -113,7 +115,8 @@ Unlike the webapp, the collector does have some dependencies:
|
|||||||
Unfortunately the version of `smartmontools` (which contains `smartctl`) available in some of the base OS repositories is ancient.
|
Unfortunately the version of `smartmontools` (which contains `smartctl`) available in some of the base OS repositories is ancient.
|
||||||
So you'll need to install the v7+ version using one of the following commands:
|
So you'll need to install the v7+ version using one of the following commands:
|
||||||
|
|
||||||
- **Ubuntu:** `apt-get install -y smartmontools=7.0-0ubuntu1~ubuntu18.04.1`
|
- **Ubuntu (22.04/Jammy/LTS):** `apt-get install -y smartmontools`
|
||||||
|
- **Ubuntu (18.04/Bionic):** `apt-get install -y smartmontools=7.0-0ubuntu1~ubuntu18.04.1`
|
||||||
- **Centos8:**
|
- **Centos8:**
|
||||||
- `dnf install https://extras.getpagespeed.com/release-el8-latest.rpm`
|
- `dnf install https://extras.getpagespeed.com/release-el8-latest.rpm`
|
||||||
- `dnf install smartmontools`
|
- `dnf install smartmontools`
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# pfsense Install
|
||||||
|
|
||||||
|
This bascially follows the [Manual collector instructions](https://github.com/AnalogJ/scrutiny/blob/master/docs/INSTALL_MANUAL.md#collector) and assumes you are running a hub and spoke deployment and already have the web app setup.
|
||||||
|
|
||||||
|
|
||||||
|
### Dependencies
|
||||||
|
|
||||||
|
SSH into pfsense, hit `8` for the shell and install the required dependencies.
|
||||||
|
|
||||||
|
```
|
||||||
|
pkg install smartmontools
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure smartmontools is v7+. This won't be a problem in pfsense 2.6.0+
|
||||||
|
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
Now let's create a directory structure to contain the Scrutiny collector binary.
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir -p /opt/scrutiny/bin
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Download Files
|
||||||
|
|
||||||
|
Next, we'll download the Scrutiny collector binary from the [latest Github release](https://github.com/analogj/scrutiny/releases).
|
||||||
|
|
||||||
|
> NOTE: Ensure you have the latest version in the below command
|
||||||
|
|
||||||
|
```
|
||||||
|
fetch -o /opt/scrutiny/bin https://github.com/AnalogJ/scrutiny/releases/download/vX.X.X/scrutiny-collector-metrics-freebsd-amd64
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Prepare Scrutiny
|
||||||
|
|
||||||
|
Now that we have downloaded the required files, let's prepare the filesystem.
|
||||||
|
|
||||||
|
```
|
||||||
|
chmod +x /opt/scrutiny/bin/scrutiny-collector-metrics-freebsd-amd64
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Start Scrutiny Collector, Populate Webapp
|
||||||
|
|
||||||
|
Next, we will manually trigger the collector, to populate the Scrutiny dashboard:
|
||||||
|
|
||||||
|
> NOTE: if you need to pass a config file to the scrutiny collector, you can provide it using the `--config` flag.
|
||||||
|
|
||||||
|
```
|
||||||
|
/opt/scrutiny/bin/scrutiny-collector-metrics-freebsd-amd64 run --api-endpoint "http://localhost:8080"
|
||||||
|
```
|
||||||
|
> NOTE: change the IP address to that of your web app
|
||||||
|
|
||||||
|
### Schedule Collector with Cron
|
||||||
|
|
||||||
|
Finally you need to schedule the collector to run periodically.
|
||||||
|
|
||||||
|
Login to the pfsense webGUI and head to `Services/Cron` add an entry with the following details:
|
||||||
|
|
||||||
|
```
|
||||||
|
Minute: */15
|
||||||
|
Hour: *
|
||||||
|
Day of the Month: *
|
||||||
|
Month of the Year: *
|
||||||
|
Day of the Week: *
|
||||||
|
User: root
|
||||||
|
Command: /opt/scrutiny/bin/scrutiny-collector-metrics-freebsd-amd64 run --api-endpoint "http://localhost:8080" >/dev/null 2>&1
|
||||||
|
```
|
||||||
|
> NOTE: `>/dev/null 2>&1` is used to stop cron confirmation emails being sent.
|
||||||
@@ -4,11 +4,13 @@ These are the officially supported NAS OS's (with documentation and setup guides
|
|||||||
Once a guide is created (in `docs/guides/`) it will be linked here.
|
Once a guide is created (in `docs/guides/`) it will be linked here.
|
||||||
|
|
||||||
- [ ] freenas/truenas
|
- [ ] freenas/truenas
|
||||||
- [x] [unraid](https://github.com/AnalogJ/scrutiny/blob/master/docs/INSTALL_UNRAID.md)
|
- [x] [unraid](./INSTALL_UNRAID.md)
|
||||||
- [ ] ESXI
|
- [ ] ESXI
|
||||||
- [ ] Proxmox
|
- [ ] Proxmox
|
||||||
- [ ] Synology
|
- [ ] Synology
|
||||||
- [ ] OMV
|
- [ ] OMV
|
||||||
- [ ] Amahi
|
- [ ] Amahi
|
||||||
- [ ] Running in a LXC container
|
- [ ] Running in a LXC container
|
||||||
|
- [x] [PFSense](./INSTALL_UNRAID.md)
|
||||||
|
- [ ] QNAP
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ If the output is the same, your devices will be processed by Scrutiny.
|
|||||||
In some cases `--scan` does not correctly detect the device type, returning [incomplete SMART data](https://github.com/AnalogJ/scrutiny/issues/45).
|
In some cases `--scan` does not correctly detect the device type, returning [incomplete SMART data](https://github.com/AnalogJ/scrutiny/issues/45).
|
||||||
Scrutiny will supports overriding the detected device type via the config file.
|
Scrutiny will supports overriding the detected device type via the config file.
|
||||||
|
|
||||||
|
[example.collector.yaml](https://github.com/AnalogJ/scrutiny/blob/master/example.collector.yaml)
|
||||||
|
|
||||||
### RAID Controllers (Megaraid/3ware/HBA/Adaptec/HPE/etc)
|
### RAID Controllers (Megaraid/3ware/HBA/Adaptec/HPE/etc)
|
||||||
Smartctl has support for a large number of [RAID controllers](https://www.smartmontools.org/wiki/Supported_RAID-Controllers), however this
|
Smartctl has support for a large number of [RAID controllers](https://www.smartmontools.org/wiki/Supported_RAID-Controllers), however this
|
||||||
support is not automatic, and may require some additional device type hinting. You can provide this information to the Scrutiny collector
|
support is not automatic, and may require some additional device type hinting. You can provide this information to the Scrutiny collector
|
||||||
@@ -111,12 +113,60 @@ instead of the block device (`/dev/nvme0n1`). See [#209](https://github.com/Anal
|
|||||||
|
|
||||||
### ATA
|
### ATA
|
||||||
|
|
||||||
### Standby/Sleeping Disks
|
### Exit Codes
|
||||||
|
|
||||||
|
If you see an error message similar to `smartctl returned an error code (2) while processing /dev/sda`, this means that
|
||||||
|
`smartctl` (not Scrutiny) exited with an error code. Scrutiny will attempt to print a helpful error message to help you debug,
|
||||||
|
but you can look at the table (and associated links) below to debug `smartctl`.
|
||||||
|
|
||||||
|
> smartctl Return Values
|
||||||
|
> The return values of smartctl are defined by a bitmask. If all is well with the disk, the return value (exit status) of
|
||||||
|
> smartctl is 0 (all bits turned off). If a problem occurs, or an error, potential error, or fault is detected, then
|
||||||
|
> a non-zero status is returned. In this case, the eight different bits in the return value have the following meanings
|
||||||
|
> for ATA disks; some of these values may also be returned for SCSI disks.
|
||||||
|
>
|
||||||
|
> source: http://www.linuxguide.it/command_line/linux-manpage/do.php?file=smartctl#sect7
|
||||||
|
|
||||||
|
|
||||||
|
| Exit Code (Isolated) | Binary | Problem Message |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | Bit 0 | Command line did not parse. |
|
||||||
|
| 2 | Bit 1 | Device open failed, or device did not return an IDENTIFY DEVICE structure. |
|
||||||
|
| 4 | Bit 2 | Some SMART command to the disk failed, or there was a checksum error in a SMART data structure (see В´-bВ´ option above). |
|
||||||
|
| 8 | Bit 3 | SMART status check returned “DISK FAILING". |
|
||||||
|
| 16 | Bit 4 | We found prefail Attributes <= threshold. |
|
||||||
|
| 32 | Bit 5 | SMART status check returned “DISK OK” but we found that some (usage or prefail) Attributes have been <= threshold at some time in the past. |
|
||||||
|
| 64 | Bit 6 | The device error log contains records of errors. |
|
||||||
|
| 128 | Bit 7 | The device self-test log contains records of errors. |
|
||||||
|
|
||||||
|
#### Standby/Sleeping Disks
|
||||||
|
|
||||||
|
Disks in Standby/Sleep can also cause `smartctl` to exit abnormally, usually with `exit code: 2`.
|
||||||
|
|
||||||
- https://github.com/AnalogJ/scrutiny/issues/221
|
- https://github.com/AnalogJ/scrutiny/issues/221
|
||||||
- https://github.com/AnalogJ/scrutiny/issues/157
|
- https://github.com/AnalogJ/scrutiny/issues/157
|
||||||
|
|
||||||
### Volume Mount All Devices (`/dev`) - Privileged
|
### Volume Mount All Devices (`/dev`) - Privileged
|
||||||
|
|
||||||
|
> WARNING: This is an insecure/dangerous workaround. Running Scrutiny (or any Docker image) with `--privileged` is equivalent to running it with root access.
|
||||||
|
|
||||||
|
If you have exhausted all other mechanisms to get your disks working with `smartctl` running within a container, you can try running the docker image with the following additional flags:
|
||||||
|
|
||||||
|
- `--privileged` (instead of `--cap-add`) - this gives the docker container full access to your system. Scrutiny does not require this permission, however it can be helpful for `smartctl`
|
||||||
|
- `-v /dev:/dev:ro` (instead of `--device`) - this mounts the `/dev` folder (containing all your device files) into the container, allowing `smartctl` to see your disks, exactly as if it were running on your host directly.
|
||||||
|
|
||||||
|
With this workaround your `docker run` command would look similar to the following:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -it --rm -p 8080:8080 -p 8086:8086 \
|
||||||
|
-v `pwd`/scrutiny:/opt/scrutiny/config \
|
||||||
|
-v `pwd`/influxdb2:/opt/scrutiny/influxdb \
|
||||||
|
-v /run/udev:/run/udev:ro \
|
||||||
|
--privileged \
|
||||||
|
-v /dev:/dev \
|
||||||
|
--name scrutiny \
|
||||||
|
ghcr.io/analogj/scrutiny:master-omnibus
|
||||||
|
```
|
||||||
|
|
||||||
## Scrutiny detects Failure but SMART Passed?
|
## Scrutiny detects Failure but SMART Passed?
|
||||||
|
|
||||||
@@ -138,3 +188,17 @@ Thankfully the collector has a special `--host-id` flag (or `COLLECTOR_HOST_ID`
|
|||||||
|
|
||||||
See the [docs/INSTALL_HUB_SPOKE.md](/docs/INSTALL_HUB_SPOKE.md) guide for more information.
|
See the [docs/INSTALL_HUB_SPOKE.md](/docs/INSTALL_HUB_SPOKE.md) guide for more information.
|
||||||
|
|
||||||
|
## Collector DEBUG mode
|
||||||
|
|
||||||
|
You can use environmental variables to enable debug logging and/or log files for the collector:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DEBUG=true
|
||||||
|
COLLECTOR_LOG_FILE=/tmp/collector.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if you're not using docker, you can pass CLI arguments to the collector during startup:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scrutiny-collector-metrics run --debug --log-file /tmp/collector.log
|
||||||
|
```
|
||||||
@@ -53,6 +53,13 @@ devices:
|
|||||||
# - 3ware,3
|
# - 3ware,3
|
||||||
# - 3ware,4
|
# - 3ware,4
|
||||||
# - 3ware,5
|
# - 3ware,5
|
||||||
|
#
|
||||||
|
# # example to show how to override the smartctl command args (per device), see below for how to override these globally.
|
||||||
|
# - device: /dev/sda
|
||||||
|
# commands:
|
||||||
|
# metrics_info_args: '--info --json -T permissive' # used to determine device unique ID & register device with Scrutiny
|
||||||
|
# metrics_smart_args: '--xall --json -T permissive' # used to retrieve smart data for each device.
|
||||||
|
|
||||||
|
|
||||||
#log:
|
#log:
|
||||||
# file: '' #absolute or relative paths allowed, eg. web.log
|
# file: '' #absolute or relative paths allowed, eg. web.log
|
||||||
@@ -64,6 +71,12 @@ devices:
|
|||||||
# if you need to use a custom base path (for a reverse proxy), you can add a suffix to the endpoint.
|
# if you need to use a custom base path (for a reverse proxy), you can add a suffix to the endpoint.
|
||||||
# See docs/TROUBLESHOOTING_REVERSE_PROXY.md for more info,
|
# See docs/TROUBLESHOOTING_REVERSE_PROXY.md for more info,
|
||||||
|
|
||||||
|
# example to show how to override the smartctl command args globally
|
||||||
|
#commands:
|
||||||
|
# metrics_scan_args: '--scan --json' # used to detect devices
|
||||||
|
# metrics_info_args: '--info --json' # used to determine device unique ID & register device with Scrutiny
|
||||||
|
# metrics_smart_args: '--xall --json' # used to retrieve smart data for each device.
|
||||||
|
|
||||||
|
|
||||||
########################################################################################################################
|
########################################################################################################################
|
||||||
# FEATURES COMING SOON
|
# FEATURES COMING SOON
|
||||||
|
|||||||
@@ -4,25 +4,34 @@ const DeviceProtocolAta = "ATA"
|
|||||||
const DeviceProtocolScsi = "SCSI"
|
const DeviceProtocolScsi = "SCSI"
|
||||||
const DeviceProtocolNvme = "NVMe"
|
const DeviceProtocolNvme = "NVMe"
|
||||||
|
|
||||||
const SmartAttributeStatusPassed = 0
|
type AttributeStatus uint8
|
||||||
const SmartAttributeStatusFailed = 1
|
|
||||||
const SmartAttributeStatusWarning = 2
|
|
||||||
|
|
||||||
const SmartWhenFailedFailingNow = "FAILING_NOW"
|
|
||||||
const SmartWhenFailedInThePast = "IN_THE_PAST"
|
|
||||||
|
|
||||||
//const SmartStatusPassed = "passed"
|
|
||||||
//const SmartStatusFailed = "failed"
|
|
||||||
|
|
||||||
type DeviceStatus int
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DeviceStatusPassed DeviceStatus = 0
|
// AttributeStatusPassed binary, 1,2,4,8,16,32,etc
|
||||||
DeviceStatusFailedSmart DeviceStatus = iota
|
AttributeStatusPassed AttributeStatus = 0
|
||||||
DeviceStatusFailedScrutiny DeviceStatus = iota
|
AttributeStatusFailedSmart AttributeStatus = 1
|
||||||
|
AttributeStatusWarningScrutiny AttributeStatus = 2
|
||||||
|
AttributeStatusFailedScrutiny AttributeStatus = 4
|
||||||
)
|
)
|
||||||
|
|
||||||
func Set(b, flag DeviceStatus) DeviceStatus { return b | flag }
|
const AttributeWhenFailedFailingNow = "FAILING_NOW"
|
||||||
func Clear(b, flag DeviceStatus) DeviceStatus { return b &^ flag }
|
const AttributeWhenFailedInThePast = "IN_THE_PAST"
|
||||||
func Toggle(b, flag DeviceStatus) DeviceStatus { return b ^ flag }
|
|
||||||
func Has(b, flag DeviceStatus) bool { return b&flag != 0 }
|
func AttributeStatusSet(b, flag AttributeStatus) AttributeStatus { return b | flag }
|
||||||
|
func AttributeStatusClear(b, flag AttributeStatus) AttributeStatus { return b &^ flag }
|
||||||
|
func AttributeStatusToggle(b, flag AttributeStatus) AttributeStatus { return b ^ flag }
|
||||||
|
func AttributeStatusHas(b, flag AttributeStatus) bool { return b&flag != 0 }
|
||||||
|
|
||||||
|
type DeviceStatus uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DeviceStatusPassed binary, 1,2,4,8,16,32,etc
|
||||||
|
DeviceStatusPassed DeviceStatus = 0
|
||||||
|
DeviceStatusFailedSmart DeviceStatus = 1
|
||||||
|
DeviceStatusFailedScrutiny DeviceStatus = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
func DeviceStatusSet(b, flag DeviceStatus) DeviceStatus { return b | flag }
|
||||||
|
func DeviceStatusClear(b, flag DeviceStatus) DeviceStatus { return b &^ flag }
|
||||||
|
func DeviceStatusToggle(b, flag DeviceStatus) DeviceStatus { return b ^ flag }
|
||||||
|
func DeviceStatusHas(b, flag DeviceStatus) bool { return b&flag != 0 }
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (sr *scrutinyRepository) UpdateDeviceStatus(ctx context.Context, wwn string
|
|||||||
return device, fmt.Errorf("Could not get device from DB: %v", err)
|
return device, fmt.Errorf("Could not get device from DB: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
device.DeviceStatus = pkg.Set(device.DeviceStatus, status)
|
device.DeviceStatus = pkg.DeviceStatusSet(device.DeviceStatus, status)
|
||||||
return device, sr.gormClient.Model(&device).Updates(device).Error
|
return device, sr.gormClient.Model(&device).Updates(device).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ type Device struct {
|
|||||||
WWN string `json:"wwn" gorm:"primary_key"`
|
WWN string `json:"wwn" gorm:"primary_key"`
|
||||||
|
|
||||||
DeviceName string `json:"device_name"`
|
DeviceName string `json:"device_name"`
|
||||||
DeviceUUID string `json:"device_uuid"`
|
DeviceUUID string `json:"device_uuid"`
|
||||||
DeviceSerialID string `json:"device_serial_id"`
|
DeviceSerialID string `json:"device_serial_id"`
|
||||||
DeviceLabel string `json:"device_label"`
|
DeviceLabel string `json:"device_label"`
|
||||||
|
|
||||||
Manufacturer string `json:"manufacturer"`
|
Manufacturer string `json:"manufacturer"`
|
||||||
ModelName string `json:"model_name"`
|
ModelName string `json:"model_name"`
|
||||||
@@ -166,7 +166,7 @@ func (dv *Device) UpdateFromCollectorSmartInfo(info collector.SmartInfo) error {
|
|||||||
dv.DeviceProtocol = info.Device.Protocol
|
dv.DeviceProtocol = info.Device.Protocol
|
||||||
|
|
||||||
if !info.SmartStatus.Passed {
|
if !info.SmartStatus.Passed {
|
||||||
dv.DeviceStatus = pkg.Set(dv.DeviceStatus, pkg.DeviceStatusFailedSmart)
|
dv.DeviceStatus = pkg.DeviceStatusSet(dv.DeviceStatus, pkg.DeviceStatusFailedSmart)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func (sm *Smart) FromCollectorSmartInfo(wwn string, info collector.SmartInfo) er
|
|||||||
sm.PowerCycleCount = info.PowerCycleCount
|
sm.PowerCycleCount = info.PowerCycleCount
|
||||||
sm.PowerOnHours = info.PowerOnTime.Hours
|
sm.PowerOnHours = info.PowerOnTime.Hours
|
||||||
if !info.SmartStatus.Passed {
|
if !info.SmartStatus.Passed {
|
||||||
sm.Status = pkg.DeviceStatusFailedSmart
|
sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedSmart)
|
||||||
}
|
}
|
||||||
|
|
||||||
sm.DeviceProtocol = info.Device.Protocol
|
sm.DeviceProtocol = info.Device.Protocol
|
||||||
@@ -148,8 +148,9 @@ func (sm *Smart) ProcessAtaSmartInfo(tableItems []collector.AtaSmartAttributesTa
|
|||||||
}
|
}
|
||||||
attrModel.PopulateAttributeStatus()
|
attrModel.PopulateAttributeStatus()
|
||||||
sm.Attributes[strconv.Itoa(collectorAttr.ID)] = &attrModel
|
sm.Attributes[strconv.Itoa(collectorAttr.ID)] = &attrModel
|
||||||
if attrModel.Status == pkg.SmartAttributeStatusFailed {
|
|
||||||
sm.Status = pkg.Set(sm.Status, pkg.DeviceStatusFailedScrutiny)
|
if pkg.AttributeStatusHas(attrModel.Status, pkg.AttributeStatusFailedScrutiny) {
|
||||||
|
sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedScrutiny)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,8 +179,8 @@ func (sm *Smart) ProcessNvmeSmartInfo(nvmeSmartHealthInformationLog collector.Nv
|
|||||||
|
|
||||||
//find analyzed attribute status
|
//find analyzed attribute status
|
||||||
for _, val := range sm.Attributes {
|
for _, val := range sm.Attributes {
|
||||||
if val.GetStatus() == pkg.SmartAttributeStatusFailed {
|
if pkg.AttributeStatusHas(val.GetStatus(), pkg.AttributeStatusFailedScrutiny) {
|
||||||
sm.Status = pkg.Set(sm.Status, pkg.DeviceStatusFailedScrutiny)
|
sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedScrutiny)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,8 +205,8 @@ func (sm *Smart) ProcessScsiSmartInfo(defectGrownList int64, scsiErrorCounterLog
|
|||||||
|
|
||||||
//find analyzed attribute status
|
//find analyzed attribute status
|
||||||
for _, val := range sm.Attributes {
|
for _, val := range sm.Attributes {
|
||||||
if val.GetStatus() == pkg.SmartAttributeStatusFailed {
|
if pkg.AttributeStatusHas(val.GetStatus(), pkg.AttributeStatusFailedScrutiny) {
|
||||||
sm.Status = pkg.Set(sm.Status, pkg.DeviceStatusFailedScrutiny)
|
sm.Status = pkg.DeviceStatusSet(sm.Status, pkg.DeviceStatusFailedScrutiny)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ type SmartAtaAttribute struct {
|
|||||||
WhenFailed string `json:"when_failed"`
|
WhenFailed string `json:"when_failed"`
|
||||||
|
|
||||||
//Generated data
|
//Generated data
|
||||||
TransformedValue int64 `json:"transformed_value"`
|
TransformedValue int64 `json:"transformed_value"`
|
||||||
Status int64 `json:"status"`
|
Status pkg.AttributeStatus `json:"status"`
|
||||||
StatusReason string `json:"status_reason,omitempty"`
|
StatusReason string `json:"status_reason,omitempty"`
|
||||||
FailureRate float64 `json:"failure_rate,omitempty"`
|
FailureRate float64 `json:"failure_rate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sa *SmartAtaAttribute) GetStatus() int64 {
|
func (sa *SmartAtaAttribute) GetStatus() pkg.AttributeStatus {
|
||||||
return sa.Status
|
return sa.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ func (sa *SmartAtaAttribute) Flatten() map[string]interface{} {
|
|||||||
|
|
||||||
//Generated Data
|
//Generated Data
|
||||||
fmt.Sprintf("attr.%s.transformed_value", idString): sa.TransformedValue,
|
fmt.Sprintf("attr.%s.transformed_value", idString): sa.TransformedValue,
|
||||||
fmt.Sprintf("attr.%s.status", idString): sa.Status,
|
fmt.Sprintf("attr.%s.status", idString): int64(sa.Status),
|
||||||
fmt.Sprintf("attr.%s.status_reason", idString): sa.StatusReason,
|
fmt.Sprintf("attr.%s.status_reason", idString): sa.StatusReason,
|
||||||
fmt.Sprintf("attr.%s.failure_rate", idString): sa.FailureRate,
|
fmt.Sprintf("attr.%s.failure_rate", idString): sa.FailureRate,
|
||||||
}
|
}
|
||||||
@@ -77,7 +77,7 @@ func (sa *SmartAtaAttribute) Inflate(key string, val interface{}) {
|
|||||||
case "transformed_value":
|
case "transformed_value":
|
||||||
sa.TransformedValue = val.(int64)
|
sa.TransformedValue = val.(int64)
|
||||||
case "status":
|
case "status":
|
||||||
sa.Status = val.(int64)
|
sa.Status = pkg.AttributeStatus(val.(int64))
|
||||||
case "status_reason":
|
case "status_reason":
|
||||||
sa.StatusReason = val.(string)
|
sa.StatusReason = val.(string)
|
||||||
case "failure_rate":
|
case "failure_rate":
|
||||||
@@ -89,16 +89,16 @@ func (sa *SmartAtaAttribute) Inflate(key string, val interface{}) {
|
|||||||
//populate attribute status, using SMART Thresholds & Observed Metadata
|
//populate attribute status, using SMART Thresholds & Observed Metadata
|
||||||
// Chainable
|
// Chainable
|
||||||
func (sa *SmartAtaAttribute) PopulateAttributeStatus() *SmartAtaAttribute {
|
func (sa *SmartAtaAttribute) PopulateAttributeStatus() *SmartAtaAttribute {
|
||||||
if strings.ToUpper(sa.WhenFailed) == pkg.SmartWhenFailedFailingNow {
|
if strings.ToUpper(sa.WhenFailed) == pkg.AttributeWhenFailedFailingNow {
|
||||||
//this attribute has previously failed
|
//this attribute has previously failed
|
||||||
sa.Status = pkg.SmartAttributeStatusFailed
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedSmart)
|
||||||
sa.StatusReason = "Attribute is failing manufacturer SMART threshold"
|
sa.StatusReason += "Attribute is failing manufacturer SMART threshold"
|
||||||
//if the Smart Status is failed, we should exit early, no need to look at thresholds.
|
//if the Smart Status is failed, we should exit early, no need to look at thresholds.
|
||||||
return sa
|
return sa
|
||||||
|
|
||||||
} else if strings.ToUpper(sa.WhenFailed) == pkg.SmartWhenFailedInThePast {
|
} else if strings.ToUpper(sa.WhenFailed) == pkg.AttributeWhenFailedInThePast {
|
||||||
sa.Status = pkg.SmartAttributeStatusWarning
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusWarningScrutiny)
|
||||||
sa.StatusReason = "Attribute has previously failed manufacturer SMART threshold"
|
sa.StatusReason += "Attribute has previously failed manufacturer SMART threshold"
|
||||||
}
|
}
|
||||||
|
|
||||||
if smartMetadata, ok := thresholds.AtaMetadata[sa.AttributeId]; ok {
|
if smartMetadata, ok := thresholds.AtaMetadata[sa.AttributeId]; ok {
|
||||||
@@ -138,16 +138,16 @@ func (sa *SmartAtaAttribute) ValidateThreshold(smartMetadata thresholds.AtaAttri
|
|||||||
|
|
||||||
if smartMetadata.Critical {
|
if smartMetadata.Critical {
|
||||||
if obsThresh.AnnualFailureRate >= 0.10 {
|
if obsThresh.AnnualFailureRate >= 0.10 {
|
||||||
sa.Status = pkg.SmartAttributeStatusFailed
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
|
||||||
sa.StatusReason = "Observed Failure Rate for Critical Attribute is greater than 10%"
|
sa.StatusReason += "Observed Failure Rate for Critical Attribute is greater than 10%"
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if obsThresh.AnnualFailureRate >= 0.20 {
|
if obsThresh.AnnualFailureRate >= 0.20 {
|
||||||
sa.Status = pkg.SmartAttributeStatusFailed
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
|
||||||
sa.StatusReason = "Observed Failure Rate for Attribute is greater than 20%"
|
sa.StatusReason += "Observed Failure Rate for Non-Critical Attribute is greater than 20%"
|
||||||
} else if obsThresh.AnnualFailureRate >= 0.10 {
|
} else if obsThresh.AnnualFailureRate >= 0.10 {
|
||||||
sa.Status = pkg.SmartAttributeStatusWarning
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusWarningScrutiny)
|
||||||
sa.StatusReason = "Observed Failure Rate for Attribute is greater than 10%"
|
sa.StatusReason += "Observed Failure Rate for Non-Critical Attribute is greater than 10%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ func (sa *SmartAtaAttribute) ValidateThreshold(smartMetadata thresholds.AtaAttri
|
|||||||
}
|
}
|
||||||
// no bucket found
|
// no bucket found
|
||||||
if smartMetadata.Critical {
|
if smartMetadata.Critical {
|
||||||
sa.Status = pkg.SmartAttributeStatusWarning
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusWarningScrutiny)
|
||||||
sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute"
|
sa.StatusReason = "Could not determine Observed Failure Rate for Critical Attribute"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package measurements
|
package measurements
|
||||||
|
|
||||||
|
import "github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||||
|
|
||||||
type SmartAttribute interface {
|
type SmartAttribute interface {
|
||||||
Flatten() (fields map[string]interface{})
|
Flatten() (fields map[string]interface{})
|
||||||
Inflate(key string, val interface{})
|
Inflate(key string, val interface{})
|
||||||
GetStatus() int64
|
GetStatus() pkg.AttributeStatus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ type SmartNvmeAttribute struct {
|
|||||||
Value int64 `json:"value"`
|
Value int64 `json:"value"`
|
||||||
Threshold int64 `json:"thresh"`
|
Threshold int64 `json:"thresh"`
|
||||||
|
|
||||||
TransformedValue int64 `json:"transformed_value"`
|
TransformedValue int64 `json:"transformed_value"`
|
||||||
Status int64 `json:"status"`
|
Status pkg.AttributeStatus `json:"status"`
|
||||||
StatusReason string `json:"status_reason,omitempty"`
|
StatusReason string `json:"status_reason,omitempty"`
|
||||||
FailureRate float64 `json:"failure_rate,omitempty"`
|
FailureRate float64 `json:"failure_rate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sa *SmartNvmeAttribute) GetStatus() int64 {
|
func (sa *SmartNvmeAttribute) GetStatus() pkg.AttributeStatus {
|
||||||
return sa.Status
|
return sa.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ func (sa *SmartNvmeAttribute) Flatten() map[string]interface{} {
|
|||||||
|
|
||||||
//Generated Data
|
//Generated Data
|
||||||
fmt.Sprintf("attr.%s.transformed_value", sa.AttributeId): sa.TransformedValue,
|
fmt.Sprintf("attr.%s.transformed_value", sa.AttributeId): sa.TransformedValue,
|
||||||
fmt.Sprintf("attr.%s.status", sa.AttributeId): sa.Status,
|
fmt.Sprintf("attr.%s.status", sa.AttributeId): int64(sa.Status),
|
||||||
fmt.Sprintf("attr.%s.status_reason", sa.AttributeId): sa.StatusReason,
|
fmt.Sprintf("attr.%s.status_reason", sa.AttributeId): sa.StatusReason,
|
||||||
fmt.Sprintf("attr.%s.failure_rate", sa.AttributeId): sa.FailureRate,
|
fmt.Sprintf("attr.%s.failure_rate", sa.AttributeId): sa.FailureRate,
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ func (sa *SmartNvmeAttribute) Inflate(key string, val interface{}) {
|
|||||||
case "transformed_value":
|
case "transformed_value":
|
||||||
sa.TransformedValue = val.(int64)
|
sa.TransformedValue = val.(int64)
|
||||||
case "status":
|
case "status":
|
||||||
sa.Status = val.(int64)
|
sa.Status = pkg.AttributeStatus(val.(int64))
|
||||||
case "status_reason":
|
case "status_reason":
|
||||||
sa.StatusReason = val.(string)
|
sa.StatusReason = val.(string)
|
||||||
case "failure_rate":
|
case "failure_rate":
|
||||||
@@ -72,8 +72,8 @@ func (sa *SmartNvmeAttribute) PopulateAttributeStatus() *SmartNvmeAttribute {
|
|||||||
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
||||||
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
||||||
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
||||||
sa.Status = pkg.SmartAttributeStatusFailed
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
|
||||||
sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
sa.StatusReason += "Attribute is failing recommended SMART threshold"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ type SmartScsiAttribute struct {
|
|||||||
Value int64 `json:"value"`
|
Value int64 `json:"value"`
|
||||||
Threshold int64 `json:"thresh"`
|
Threshold int64 `json:"thresh"`
|
||||||
|
|
||||||
TransformedValue int64 `json:"transformed_value"`
|
TransformedValue int64 `json:"transformed_value"`
|
||||||
Status int64 `json:"status"`
|
Status pkg.AttributeStatus `json:"status"`
|
||||||
StatusReason string `json:"status_reason,omitempty"`
|
StatusReason string `json:"status_reason,omitempty"`
|
||||||
FailureRate float64 `json:"failure_rate,omitempty"`
|
FailureRate float64 `json:"failure_rate,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sa *SmartScsiAttribute) GetStatus() int64 {
|
func (sa *SmartScsiAttribute) GetStatus() pkg.AttributeStatus {
|
||||||
return sa.Status
|
return sa.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ func (sa *SmartScsiAttribute) Flatten() map[string]interface{} {
|
|||||||
|
|
||||||
//Generated Data
|
//Generated Data
|
||||||
fmt.Sprintf("attr.%s.transformed_value", sa.AttributeId): sa.TransformedValue,
|
fmt.Sprintf("attr.%s.transformed_value", sa.AttributeId): sa.TransformedValue,
|
||||||
fmt.Sprintf("attr.%s.status", sa.AttributeId): sa.Status,
|
fmt.Sprintf("attr.%s.status", sa.AttributeId): int64(sa.Status),
|
||||||
fmt.Sprintf("attr.%s.status_reason", sa.AttributeId): sa.StatusReason,
|
fmt.Sprintf("attr.%s.status_reason", sa.AttributeId): sa.StatusReason,
|
||||||
fmt.Sprintf("attr.%s.failure_rate", sa.AttributeId): sa.FailureRate,
|
fmt.Sprintf("attr.%s.failure_rate", sa.AttributeId): sa.FailureRate,
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,7 @@ func (sa *SmartScsiAttribute) Inflate(key string, val interface{}) {
|
|||||||
case "transformed_value":
|
case "transformed_value":
|
||||||
sa.TransformedValue = val.(int64)
|
sa.TransformedValue = val.(int64)
|
||||||
case "status":
|
case "status":
|
||||||
sa.Status = val.(int64)
|
sa.Status = pkg.AttributeStatus(val.(int64))
|
||||||
case "status_reason":
|
case "status_reason":
|
||||||
sa.StatusReason = val.(string)
|
sa.StatusReason = val.(string)
|
||||||
case "failure_rate":
|
case "failure_rate":
|
||||||
@@ -73,7 +73,7 @@ func (sa *SmartScsiAttribute) PopulateAttributeStatus() *SmartScsiAttribute {
|
|||||||
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
//check what the ideal is. Ideal tells us if we our recorded value needs to be above, or below the threshold
|
||||||
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
if (smartMetadata.Ideal == "low" && sa.Value > sa.Threshold) ||
|
||||||
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
(smartMetadata.Ideal == "high" && sa.Value < sa.Threshold) {
|
||||||
sa.Status = pkg.SmartAttributeStatusFailed
|
sa.Status = pkg.AttributeStatusSet(sa.Status, pkg.AttributeStatusFailedScrutiny)
|
||||||
sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
sa.StatusReason = "Attribute is failing recommended SMART threshold"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,9 +328,12 @@ func TestFromCollectorSmartInfo(t *testing.T) {
|
|||||||
require.Equal(t, 18, len(smartMdl.Attributes))
|
require.Equal(t, 18, len(smartMdl.Attributes))
|
||||||
|
|
||||||
//check that temperature was correctly parsed
|
//check that temperature was correctly parsed
|
||||||
|
|
||||||
require.Equal(t, int64(163210330144), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).RawValue)
|
require.Equal(t, int64(163210330144), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).RawValue)
|
||||||
require.Equal(t, int64(32), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).TransformedValue)
|
require.Equal(t, int64(32), smartMdl.Attributes["194"].(*measurements.SmartAtaAttribute).TransformedValue)
|
||||||
|
|
||||||
|
//ensure that Scrutiny warning for a non critical attribute does not set device status to failed.
|
||||||
|
require.Equal(t, pkg.AttributeStatusWarningScrutiny, smartMdl.Attributes["3"].GetStatus())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFromCollectorSmartInfo_Fail_Smart(t *testing.T) {
|
func TestFromCollectorSmartInfo_Fail_Smart(t *testing.T) {
|
||||||
@@ -402,7 +405,7 @@ func TestFromCollectorSmartInfo_Fail_ScrutinyNonCriticalFailed(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||||
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
|
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
|
||||||
require.Equal(t, int64(pkg.SmartAttributeStatusFailed), smartMdl.Attributes["199"].GetStatus(),
|
require.Equal(t, pkg.AttributeStatusFailedScrutiny, smartMdl.Attributes["199"].GetStatus(),
|
||||||
"scrutiny should detect that %d failed (status: %d, %s)",
|
"scrutiny should detect that %d failed (status: %d, %s)",
|
||||||
smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).AttributeId,
|
smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).AttributeId,
|
||||||
smartMdl.Attributes["199"].GetStatus(), smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).StatusReason,
|
smartMdl.Attributes["199"].GetStatus(), smartMdl.Attributes["199"].(*measurements.SmartAtaAttribute).StatusReason,
|
||||||
@@ -435,7 +438,7 @@ func TestFromCollectorSmartInfo_NVMe_Fail_Scrutiny(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
require.Equal(t, "WWN-test", smartMdl.DeviceWWN)
|
||||||
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
|
require.Equal(t, pkg.DeviceStatusFailedScrutiny, smartMdl.Status)
|
||||||
require.Equal(t, int64(pkg.SmartAttributeStatusFailed), smartMdl.Attributes["media_errors"].GetStatus(),
|
require.Equal(t, pkg.AttributeStatusFailedScrutiny, smartMdl.Attributes["media_errors"].GetStatus(),
|
||||||
"scrutiny should detect that %s failed (status: %d, %s)",
|
"scrutiny should detect that %s failed (status: %d, %s)",
|
||||||
smartMdl.Attributes["media_errors"].(*measurements.SmartNvmeAttribute).AttributeId,
|
smartMdl.Attributes["media_errors"].(*measurements.SmartNvmeAttribute).AttributeId,
|
||||||
smartMdl.Attributes["media_errors"].GetStatus(),
|
smartMdl.Attributes["media_errors"].GetStatus(),
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ package version
|
|||||||
|
|
||||||
// VERSION is the app-global version string, which will be replaced with a
|
// VERSION is the app-global version string, which will be replaced with a
|
||||||
// new value during packaging
|
// new value during packaging
|
||||||
const VERSION = "0.4.8"
|
const VERSION = "0.4.9"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AppEngine struct {
|
type AppEngine struct {
|
||||||
@@ -68,6 +69,11 @@ func (ae *AppEngine) Setup(logger logrus.FieldLogger) *gin.Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ae *AppEngine) Start() error {
|
func (ae *AppEngine) Start() error {
|
||||||
|
//set the gin mode
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
if strings.ToLower(ae.Config.GetString("log.level")) == "debug" {
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
}
|
||||||
|
|
||||||
logger := logrus.New()
|
logger := logrus.New()
|
||||||
//set default log level
|
//set default log level
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@angular/core';
|
|||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { TREO_APP_CONFIG } from '@treo/services/config/config.constants';
|
import { TREO_APP_CONFIG } from '@treo/services/config/config.constants';
|
||||||
|
import { AppConfig } from 'app/core/config/app.config';
|
||||||
|
|
||||||
const SCRUTINY_CONFIG_LOCAL_STORAGE_KEY = 'scrutiny';
|
const SCRUTINY_CONFIG_LOCAL_STORAGE_KEY = 'scrutiny';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ export class TreoConfigService
|
|||||||
{
|
{
|
||||||
// Private
|
// Private
|
||||||
private _config: BehaviorSubject<any>;
|
private _config: BehaviorSubject<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
@@ -20,12 +22,11 @@ export class TreoConfigService
|
|||||||
let currentScrutinyConfig = defaultConfig
|
let currentScrutinyConfig = defaultConfig
|
||||||
|
|
||||||
let localConfigStr = localStorage.getItem(SCRUTINY_CONFIG_LOCAL_STORAGE_KEY)
|
let localConfigStr = localStorage.getItem(SCRUTINY_CONFIG_LOCAL_STORAGE_KEY)
|
||||||
if(localConfigStr){
|
if (localConfigStr){
|
||||||
//check localstorage for a value
|
//check localstorage for a value
|
||||||
let localConfig = JSON.parse(localConfigStr)
|
let localConfig = JSON.parse(localConfigStr)
|
||||||
currentScrutinyConfig = localConfig
|
currentScrutinyConfig = Object.assign({}, localConfig, currentScrutinyConfig) // make sure defaults are available if missing from localStorage.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the private defaults
|
// Set the private defaults
|
||||||
this._config = new BehaviorSubject(currentScrutinyConfig);
|
this._config = new BehaviorSubject(currentScrutinyConfig);
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,7 @@ export class TreoConfigService
|
|||||||
set config(value: any)
|
set config(value: any)
|
||||||
{
|
{
|
||||||
// Merge the new config over to the current config
|
// Merge the new config over to the current config
|
||||||
const config = _.merge({}, this._config.getValue(), value);
|
let config = _.merge({}, this._config.getValue(), value);
|
||||||
|
|
||||||
//Store the config in localstorage
|
//Store the config in localstorage
|
||||||
localStorage.setItem(SCRUTINY_CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
|
localStorage.setItem(SCRUTINY_CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
|
||||||
@@ -56,6 +57,10 @@ export class TreoConfigService
|
|||||||
return this._config.asObservable();
|
return this._config.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
// @ Private methods
|
||||||
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
// @ Public methods
|
// @ Public methods
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Layout } from "app/layout/layout.types";
|
import { Layout } from "app/layout/layout.types";
|
||||||
|
|
||||||
// Theme type
|
// Theme type
|
||||||
export type Theme = "light" | "dark";
|
export type Theme = "light" | "dark" | "system";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppConfig interface. Update this interface to strictly type your config
|
* AppConfig interface. Update this interface to strictly type your config
|
||||||
|
|||||||
+24
-12
@@ -2,6 +2,18 @@
|
|||||||
<mat-dialog-content class="mat-typography">
|
<mat-dialog-content class="mat-typography">
|
||||||
|
|
||||||
<div class="flex flex-col p-8 pb-0 overflow-hidden">
|
<div class="flex flex-col p-8 pb-0 overflow-hidden">
|
||||||
|
<div class="flex flex-col mt-5 gt-md:flex-row">
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3">
|
||||||
|
<mat-label>Dark Mode</mat-label>
|
||||||
|
<mat-select [(ngModel)]="theme">
|
||||||
|
<mat-option value="system">System</mat-option>
|
||||||
|
<mat-option value="dark">Dark</mat-option>
|
||||||
|
<mat-option value="light">Light</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col mt-5 gt-md:flex-row">
|
<div class="flex flex-col mt-5 gt-md:flex-row">
|
||||||
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3">
|
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3">
|
||||||
<mat-label>Display Title</mat-label>
|
<mat-label>Display Title</mat-label>
|
||||||
@@ -38,24 +50,24 @@
|
|||||||
<mat-tab-group mat-align-tabs="start">
|
<mat-tab-group mat-align-tabs="start">
|
||||||
<mat-tab label="Ata">
|
<mat-tab label="Ata">
|
||||||
|
|
||||||
<div matTooltip="not yet implemented" class="gray-200 flex flex-col mt-5 gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="flex flex-col mt-5 gt-md:flex-row">
|
||||||
<mat-form-field class="flex-auto gt-md:pr-3">
|
<mat-form-field class="flex-auto gt-md:pr-3">
|
||||||
<mat-label>Critical Error Threshold</mat-label>
|
<mat-label class="text-hint">Critical Error Threshold</mat-label>
|
||||||
<input disabled matInput [value]="'10%'">
|
<input disabled matInput [value]="'10%'">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="flex-auto gt-md:pl-3">
|
<mat-form-field class="flex-auto gt-md:pl-3">
|
||||||
<mat-label>Critical Warning Threshold</mat-label>
|
<mat-label class="text-hint">Critical Warning Threshold</mat-label>
|
||||||
<input disabled matInput>
|
<input disabled matInput>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div matTooltip="not yet implemented" class="gray-200 flex flex-col gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="flex flex-col gt-md:flex-row">
|
||||||
<mat-form-field class="flex-auto gt-md:pr-3">
|
<mat-form-field class="flex-auto gt-md:pr-3">
|
||||||
<mat-label>Error Threshold</mat-label>
|
<mat-label class="text-hint">Error Threshold</mat-label>
|
||||||
<input disabled matInput [value]="'20%'">
|
<input disabled matInput [value]="'20%'">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="flex-auto gt-md:pl-3">
|
<mat-form-field class="flex-auto gt-md:pl-3">
|
||||||
<mat-label>Warning Threshold</mat-label>
|
<mat-label class="text-hint">Warning Threshold</mat-label>
|
||||||
<input disabled matInput [value]="'10%'">
|
<input disabled matInput [value]="'10%'">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@@ -63,26 +75,26 @@
|
|||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="NVMe">
|
<mat-tab label="NVMe">
|
||||||
|
|
||||||
<div matTooltip="not yet implemented" class="gray-200 flex flex-col mt-5 gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="flex flex-col mt-5 gt-md:flex-row">
|
||||||
<mat-form-field class="flex-auto gt-md:pr-3">
|
<mat-form-field class="flex-auto gt-md:pr-3">
|
||||||
<mat-label>Critical Error Threshold</mat-label>
|
<mat-label class="text-hint">Critical Error Threshold</mat-label>
|
||||||
<input disabled matInput [value]="'enabled'">
|
<input disabled matInput [value]="'enabled'">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="flex-auto gt-md:pl-3">
|
<mat-form-field class="flex-auto gt-md:pl-3">
|
||||||
<mat-label>Critical Warning Threshold</mat-label>
|
<mat-label class="text-hint">Critical Warning Threshold</mat-label>
|
||||||
<input disabled matInput>
|
<input disabled matInput>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="SCSI">
|
<mat-tab label="SCSI">
|
||||||
<div matTooltip="not yet implemented" class="gray-200 flex flex-col mt-5 gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="flex flex-col mt-5 gt-md:flex-row">
|
||||||
<mat-form-field class="flex-auto gt-md:pr-3">
|
<mat-form-field class="flex-auto gt-md:pr-3">
|
||||||
<mat-label>Critical Error Threshold</mat-label>
|
<mat-label class="text-hint">Critical Error Threshold</mat-label>
|
||||||
<input disabled matInput [value]="'enabled'">
|
<input disabled matInput [value]="'enabled'">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field class="flex-auto gt-md:pl-3">
|
<mat-form-field class="flex-auto gt-md:pl-3">
|
||||||
<mat-label>Critical Warning Threshold</mat-label>
|
<mat-label class="text-hint">Critical Warning Threshold</mat-label>
|
||||||
<input disabled matInput>
|
<input disabled matInput>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+8
-1
@@ -13,7 +13,8 @@ export class DashboardSettingsComponent implements OnInit {
|
|||||||
|
|
||||||
dashboardDisplay: string;
|
dashboardDisplay: string;
|
||||||
dashboardSort: string;
|
dashboardSort: string;
|
||||||
temperatureUnit: string
|
temperatureUnit: string;
|
||||||
|
theme: string;
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
private _unsubscribeAll: Subject<any>;
|
private _unsubscribeAll: Subject<any>;
|
||||||
@@ -35,14 +36,20 @@ export class DashboardSettingsComponent implements OnInit {
|
|||||||
this.dashboardDisplay = config.dashboardDisplay;
|
this.dashboardDisplay = config.dashboardDisplay;
|
||||||
this.dashboardSort = config.dashboardSort;
|
this.dashboardSort = config.dashboardSort;
|
||||||
this.temperatureUnit = config.temperatureUnit;
|
this.temperatureUnit = config.temperatureUnit;
|
||||||
|
this.theme = config.theme;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
saveSettings(): void {
|
saveSettings(): void {
|
||||||
|
|
||||||
|
|
||||||
const newSettings = {
|
const newSettings = {
|
||||||
dashboardDisplay: this.dashboardDisplay,
|
dashboardDisplay: this.dashboardDisplay,
|
||||||
dashboardSort: this.dashboardSort,
|
dashboardSort: this.dashboardSort,
|
||||||
temperatureUnit: this.temperatureUnit,
|
temperatureUnit: this.temperatureUnit,
|
||||||
|
theme: this.theme
|
||||||
}
|
}
|
||||||
this._configService.config = newSettings
|
this._configService.config = newSettings
|
||||||
console.log(`Saved Settings: ${JSON.stringify(newSettings)}`)
|
console.log(`Saved Settings: ${JSON.stringify(newSettings)}`)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
|
|
||||||
// Private
|
// Private
|
||||||
private _unsubscribeAll: Subject<any>;
|
private _unsubscribeAll: Subject<any>;
|
||||||
|
private systemPrefersDark: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -43,6 +44,9 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
{
|
{
|
||||||
// Set the private defaults
|
// Set the private defaults
|
||||||
this._unsubscribeAll = new Subject();
|
this._unsubscribeAll = new Subject();
|
||||||
|
|
||||||
|
this.systemPrefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
@@ -66,7 +70,7 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
this.theme = config.theme;
|
this.theme = config.theme;
|
||||||
|
|
||||||
// Update the selected theme class name on body
|
// Update the selected theme class name on body
|
||||||
const themeName = 'treo-theme-' + config.theme;
|
const themeName = 'treo-theme-' + this.determineTheme(config);
|
||||||
this._document.body.classList.forEach((className) => {
|
this._document.body.classList.forEach((className) => {
|
||||||
if ( className.startsWith('treo-theme-') && className !== themeName )
|
if ( className.startsWith('treo-theme-') && className !== themeName )
|
||||||
{
|
{
|
||||||
@@ -105,6 +109,17 @@ export class LayoutComponent implements OnInit, OnDestroy
|
|||||||
// @ Private methods
|
// @ Private methods
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if theme should be set to dark based on config & system settings
|
||||||
|
*/
|
||||||
|
private determineTheme(config:AppConfig): string {
|
||||||
|
if (config.theme === 'system') {
|
||||||
|
return this.systemPrefersDark ? 'dark' : 'light'
|
||||||
|
} else {
|
||||||
|
return config.theme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the selected layout
|
* Update the selected layout
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -71,6 +71,16 @@
|
|||||||
<div>{{device?.host_id}}</div>
|
<div>{{device?.host_id}}</div>
|
||||||
<div class="text-secondary text-md">Host ID</div>
|
<div class="text-secondary text-md">Host ID</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="device?.device_uuid" class="my-2 col-span-2 lt-md:col-span-1">
|
||||||
|
<div>{{device?.device_uuid}}</div>
|
||||||
|
<div class="text-secondary text-md">Device UUID</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="device?.device_label" class="my-2 col-span-2 lt-md:col-span-1">
|
||||||
|
<div>{{device?.device_label}}</div>
|
||||||
|
<div class="text-secondary text-md">Device Label</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="device?.device_type && device?.device_type != 'ata' && device?.device_type != 'scsi'" class="my-2 col-span-2 lt-md:col-span-1">
|
<div *ngIf="device?.device_type && device?.device_type != 'ata' && device?.device_type != 'scsi'" class="my-2 col-span-2 lt-md:col-span-1">
|
||||||
<div>{{device?.device_type | uppercase}}</div>
|
<div>{{device?.device_type | uppercase}}</div>
|
||||||
<div class="text-secondary text-md">Device Type</div>
|
<div class="text-secondary text-md">Device Type</div>
|
||||||
|
|||||||
@@ -121,25 +121,34 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
// @ Private methods
|
// @ Private methods
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
getAttributeStatusName(attribute_status){
|
getAttributeStatusName(attributeStatus: number): string {
|
||||||
if(attribute_status == 0){
|
// tslint:disable:no-bitwise
|
||||||
return "passed"
|
|
||||||
} else if (attribute_status == 1){
|
// from Constants.go
|
||||||
return "failed"
|
// AttributeStatusPassed AttributeStatus = 0
|
||||||
} else if (attribute_status == 2){
|
// AttributeStatusFailedSmart AttributeStatus = 1
|
||||||
return "warn"
|
// AttributeStatusWarningScrutiny AttributeStatus = 2
|
||||||
|
// AttributeStatusFailedScrutiny AttributeStatus = 4
|
||||||
|
|
||||||
|
if(attributeStatus === 0){
|
||||||
|
return 'passed'
|
||||||
|
|
||||||
|
} else if ((attributeStatus & 1) !== 0 || (attributeStatus & 4) !== 0 ){
|
||||||
|
return 'failed'
|
||||||
|
} else if ((attributeStatus & 2) !== 0){
|
||||||
|
return 'warn'
|
||||||
}
|
}
|
||||||
return
|
return ''
|
||||||
|
// tslint:enable:no-bitwise
|
||||||
}
|
}
|
||||||
|
|
||||||
getAttributeName(attribute_data){
|
getAttributeName(attribute_data): string {
|
||||||
let attribute_metadata = this.metadata[attribute_data.attribute_id]
|
let attribute_metadata = this.metadata[attribute_data.attribute_id]
|
||||||
if(!attribute_metadata){
|
if(!attribute_metadata){
|
||||||
return 'Unknown Attribute Name'
|
return 'Unknown Attribute Name'
|
||||||
} else {
|
} else {
|
||||||
return attribute_metadata.display_name
|
return attribute_metadata.display_name
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
getAttributeDescription(attribute_data){
|
getAttributeDescription(attribute_data){
|
||||||
let attribute_metadata = this.metadata[attribute_data.attribute_id]
|
let attribute_metadata = this.metadata[attribute_data.attribute_id]
|
||||||
|
|||||||
@@ -4,3 +4,15 @@
|
|||||||
// @ Styles from this file will override anything from 'vendors.scss' file allowing customizations and
|
// @ Styles from this file will override anything from 'vendors.scss' file allowing customizations and
|
||||||
// modifications of third party libraries.
|
// modifications of third party libraries.
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.treo-theme-dark .yellow-50 {
|
||||||
|
background-color: #242b38 !important;
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
color: #0694a2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-secondary {
|
||||||
|
color: #0694a2 !important
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user