commit
550fb542d4
@ -1,17 +1,18 @@
|
|||||||
# Officially Supported NAS OS's
|
# Officially Supported NAS/OS's
|
||||||
|
|
||||||
These are the officially supported NAS OS's (with documentation and setup guides).
|
These are the officially supported NAS OS's (with documentation and setup guides). Once a guide is created (
|
||||||
Once a guide is created (in `docs/guides/`) it will be linked here.
|
in `docs/guides/` or elsewhere) it will be linked here.
|
||||||
|
|
||||||
- [ ] freenas/truenas
|
- [x] [freenas/truenas](https://blog.stefandroid.com/2022/01/14/smart-scrutiny.html)
|
||||||
- [x] [unraid](./INSTALL_UNRAID.md)
|
- [x] [unraid](./INSTALL_UNRAID.md)
|
||||||
- [ ] ESXI
|
- [ ] ESXI
|
||||||
- [ ] Proxmox
|
- [ ] Proxmox
|
||||||
- [x] Synology(./INSTALL_SYNOLOGY_COLLECTOR.md)
|
- [x] [Synology](./INSTALL_SYNOLOGY_COLLECTOR.md)
|
||||||
- [ ] OMV
|
- [ ] OMV
|
||||||
- [ ] Amahi
|
- [ ] Amahi
|
||||||
- [ ] Running in a LXC container
|
- [ ] Running in a LXC container
|
||||||
- [x] [PFSense](./INSTALL_UNRAID.md)
|
- [x] [PFSense](./INSTALL_UNRAID.md)
|
||||||
- [ ] QNAP
|
- [x] QNAP
|
||||||
- [ ] RockStor
|
- [x] [RockStor](https://rockstor.com/docs/interface/docker-based-rock-ons/scrutiny.html)
|
||||||
|
- [ ] Solaris/OmniOS CE Support
|
||||||
|
- [ ] Kubernetes
|
||||||
|
@ -0,0 +1,164 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
mock_config "github.com/analogj/scrutiny/webapp/backend/pkg/config/mock"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_DownsampleScript_Weekly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := "weekly"
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.DownsampleScript(aggregationType, "tsk-weekly-aggr", "0 1 * * 0")
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `
|
||||||
|
option task = {
|
||||||
|
name: "tsk-weekly-aggr",
|
||||||
|
cron: "0 1 * * 0",
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceBucket = "metrics"
|
||||||
|
rangeStart = -2w
|
||||||
|
rangeEnd = -1w
|
||||||
|
aggWindow = 1w
|
||||||
|
destBucket = "metrics_weekly"
|
||||||
|
destOrg = "scrutiny"
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: rangeStart, stop: rangeEnd)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "smart" )
|
||||||
|
|> group(columns: ["device_wwn", "_field"])
|
||||||
|
|> aggregateWindow(every: aggWindow, fn: last, createEmpty: false)
|
||||||
|
|> to(bucket: destBucket, org: destOrg)
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: rangeStart, stop: rangeEnd)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp")
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|> aggregateWindow(fn: mean, every: aggWindow, createEmpty: false)
|
||||||
|
|> set(key: "_measurement", value: "temp")
|
||||||
|
|> set(key: "_field", value: "temp")
|
||||||
|
|> to(bucket: destBucket, org: destOrg)
|
||||||
|
`, influxDbScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DownsampleScript_Monthly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := "monthly"
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.DownsampleScript(aggregationType, "tsk-monthly-aggr", "30 1 1 * *")
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `
|
||||||
|
option task = {
|
||||||
|
name: "tsk-monthly-aggr",
|
||||||
|
cron: "30 1 1 * *",
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceBucket = "metrics_weekly"
|
||||||
|
rangeStart = -2mo
|
||||||
|
rangeEnd = -1mo
|
||||||
|
aggWindow = 1mo
|
||||||
|
destBucket = "metrics_monthly"
|
||||||
|
destOrg = "scrutiny"
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: rangeStart, stop: rangeEnd)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "smart" )
|
||||||
|
|> group(columns: ["device_wwn", "_field"])
|
||||||
|
|> aggregateWindow(every: aggWindow, fn: last, createEmpty: false)
|
||||||
|
|> to(bucket: destBucket, org: destOrg)
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: rangeStart, stop: rangeEnd)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp")
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|> aggregateWindow(fn: mean, every: aggWindow, createEmpty: false)
|
||||||
|
|> set(key: "_measurement", value: "temp")
|
||||||
|
|> set(key: "_field", value: "temp")
|
||||||
|
|> to(bucket: destBucket, org: destOrg)
|
||||||
|
`, influxDbScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_DownsampleScript_Yearly(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := "yearly"
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.DownsampleScript(aggregationType, "tsk-yearly-aggr", "0 2 1 1 *")
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `
|
||||||
|
option task = {
|
||||||
|
name: "tsk-yearly-aggr",
|
||||||
|
cron: "0 2 1 1 *",
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceBucket = "metrics_monthly"
|
||||||
|
rangeStart = -2y
|
||||||
|
rangeEnd = -1y
|
||||||
|
aggWindow = 1y
|
||||||
|
destBucket = "metrics_yearly"
|
||||||
|
destOrg = "scrutiny"
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: rangeStart, stop: rangeEnd)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "smart" )
|
||||||
|
|> group(columns: ["device_wwn", "_field"])
|
||||||
|
|> aggregateWindow(every: aggWindow, fn: last, createEmpty: false)
|
||||||
|
|> to(bucket: destBucket, org: destOrg)
|
||||||
|
|
||||||
|
from(bucket: sourceBucket)
|
||||||
|
|> range(start: rangeStart, stop: rangeEnd)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp")
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|> aggregateWindow(fn: mean, every: aggWindow, createEmpty: false)
|
||||||
|
|> set(key: "_measurement", value: "temp")
|
||||||
|
|> set(key: "_field", value: "temp")
|
||||||
|
|> to(bucket: destBucket, org: destOrg)
|
||||||
|
`, influxDbScript)
|
||||||
|
}
|
@ -0,0 +1,185 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
mock_config "github.com/analogj/scrutiny/webapp/backend/pkg/config/mock"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_aggregateTempQuery_Week(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := DURATION_KEY_WEEK
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.aggregateTempQuery(aggregationType)
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `import "influxdata/influxdb/schema"
|
||||||
|
weekData = from(bucket: "metrics")
|
||||||
|
|> range(start: -1w, stop: now())
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
weekData
|
||||||
|
|> schema.fieldsAsCols()
|
||||||
|
|> yield()`, influxDbScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_aggregateTempQuery_Month(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := DURATION_KEY_MONTH
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.aggregateTempQuery(aggregationType)
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `import "influxdata/influxdb/schema"
|
||||||
|
weekData = from(bucket: "metrics")
|
||||||
|
|> range(start: -1w, stop: now())
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
monthData = from(bucket: "metrics_weekly")
|
||||||
|
|> range(start: -1mo, stop: -1w)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
union(tables: [weekData, monthData])
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> sort(columns: ["_time"], desc: false)
|
||||||
|
|> schema.fieldsAsCols()`, influxDbScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_aggregateTempQuery_Year(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := DURATION_KEY_YEAR
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.aggregateTempQuery(aggregationType)
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `import "influxdata/influxdb/schema"
|
||||||
|
weekData = from(bucket: "metrics")
|
||||||
|
|> range(start: -1w, stop: now())
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
monthData = from(bucket: "metrics_weekly")
|
||||||
|
|> range(start: -1mo, stop: -1w)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
yearData = from(bucket: "metrics_monthly")
|
||||||
|
|> range(start: -1y, stop: -1mo)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
union(tables: [weekData, monthData, yearData])
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> sort(columns: ["_time"], desc: false)
|
||||||
|
|> schema.fieldsAsCols()`, influxDbScript)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_aggregateTempQuery_Forever(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
//setup
|
||||||
|
mockCtrl := gomock.NewController(t)
|
||||||
|
defer mockCtrl.Finish()
|
||||||
|
fakeConfig := mock_config.NewMockInterface(mockCtrl)
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.bucket").Return("metrics").AnyTimes()
|
||||||
|
fakeConfig.EXPECT().GetString("web.influxdb.org").Return("scrutiny").AnyTimes()
|
||||||
|
|
||||||
|
deviceRepo := scrutinyRepository{
|
||||||
|
appConfig: fakeConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregationType := DURATION_KEY_FOREVER
|
||||||
|
|
||||||
|
//test
|
||||||
|
influxDbScript := deviceRepo.aggregateTempQuery(aggregationType)
|
||||||
|
|
||||||
|
//assert
|
||||||
|
require.Equal(t, `import "influxdata/influxdb/schema"
|
||||||
|
weekData = from(bucket: "metrics")
|
||||||
|
|> range(start: -1w, stop: now())
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
monthData = from(bucket: "metrics_weekly")
|
||||||
|
|> range(start: -1mo, stop: -1w)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
yearData = from(bucket: "metrics_monthly")
|
||||||
|
|> range(start: -1y, stop: -1mo)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
foreverData = from(bucket: "metrics_yearly")
|
||||||
|
|> range(start: -10y, stop: -1y)
|
||||||
|
|> filter(fn: (r) => r["_measurement"] == "temp" )
|
||||||
|
|> aggregateWindow(every: 1h, fn: mean, createEmpty: false)
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> toInt()
|
||||||
|
|
||||||
|
union(tables: [weekData, monthData, yearData, foreverData])
|
||||||
|
|> group(columns: ["device_wwn"])
|
||||||
|
|> sort(columns: ["_time"], desc: false)
|
||||||
|
|> schema.fieldsAsCols()`, influxDbScript)
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
import {DeviceModel} from 'app/core/models/device-model';
|
||||||
|
import {SmartModel} from 'app/core/models/measurements/smart-model';
|
||||||
|
import {AttributeMetadataModel} from 'app/core/models/thresholds/attribute-metadata-model';
|
||||||
|
|
||||||
|
// maps to webapp/backend/pkg/models/device_summary.go
|
||||||
|
export interface DeviceDetailsResponseWrapper {
|
||||||
|
success: boolean;
|
||||||
|
errors?: any[];
|
||||||
|
data: {
|
||||||
|
device: DeviceModel;
|
||||||
|
smart_results: SmartModel[];
|
||||||
|
},
|
||||||
|
metadata: { [key: string]: AttributeMetadataModel } | { [key: number]: AttributeMetadataModel };
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
// maps to webapp/backend/pkg/models/device.go
|
||||||
|
export interface DeviceModel {
|
||||||
|
wwn: string;
|
||||||
|
device_name?: string;
|
||||||
|
device_uuid?: string;
|
||||||
|
device_serial_id?: string;
|
||||||
|
device_label?: string;
|
||||||
|
|
||||||
|
manufacturer: string;
|
||||||
|
model_name: string;
|
||||||
|
interface_type: string;
|
||||||
|
interface_speed: string;
|
||||||
|
serial_number: string;
|
||||||
|
firmware: string;
|
||||||
|
rotational_speed: number;
|
||||||
|
capacity: number;
|
||||||
|
form_factor: string;
|
||||||
|
smart_support: boolean;
|
||||||
|
device_protocol: string;
|
||||||
|
device_type: string;
|
||||||
|
|
||||||
|
label: string;
|
||||||
|
host_id: string;
|
||||||
|
|
||||||
|
device_status: number;
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
import {DeviceModel} from 'app/core/models/device-model';
|
||||||
|
import {SmartTemperatureModel} from 'app/core/models/measurements/smart-temperature-model';
|
||||||
|
|
||||||
|
// maps to webapp/backend/pkg/models/device_summary.go
|
||||||
|
export interface DeviceSummaryModel {
|
||||||
|
device: DeviceModel;
|
||||||
|
smart?: SmartSummary;
|
||||||
|
temp_history?: SmartTemperatureModel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SmartSummary {
|
||||||
|
collector_date?: string,
|
||||||
|
temp?: number
|
||||||
|
power_on_hours?: number
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
|||||||
|
import {DeviceSummaryModel} from 'app/core/models/device-summary-model';
|
||||||
|
|
||||||
|
// maps to webapp/backend/pkg/models/device_summary.go
|
||||||
|
export interface DeviceSummaryResponseWrapper {
|
||||||
|
success: boolean;
|
||||||
|
errors: any[];
|
||||||
|
data: {
|
||||||
|
summary: { [key: string]: DeviceSummaryModel }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import {SmartTemperatureModel} from './measurements/smart-temperature-model';
|
||||||
|
|
||||||
|
export interface DeviceSummaryTempResponseWrapper {
|
||||||
|
success: boolean;
|
||||||
|
errors: any[];
|
||||||
|
data: {
|
||||||
|
temp_history: { [key: string]: SmartTemperatureModel[]; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
// maps to webapp/backend/pkg/models/measurements/smart_ata_attribute.go
|
||||||
|
// maps to webapp/backend/pkg/models/measurements/smart_nvme_attribute.go
|
||||||
|
// maps to webapp/backend/pkg/models/measurements/smart_scsi_attribute.go
|
||||||
|
export interface SmartAttributeModel {
|
||||||
|
attribute_id: number | string
|
||||||
|
value: number
|
||||||
|
thresh: number
|
||||||
|
worst?: number
|
||||||
|
raw_value?: number
|
||||||
|
raw_string?: string
|
||||||
|
when_failed?: string
|
||||||
|
|
||||||
|
transformed_value: number
|
||||||
|
status: number
|
||||||
|
status_reason?: string
|
||||||
|
failure_rate?: number
|
||||||
|
|
||||||
|
chartData?: any[]
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
// maps to webapp/backend/pkg/models/measurements/smart.go
|
||||||
|
import {SmartAttributeModel} from './smart-attribute-model';
|
||||||
|
|
||||||
|
export interface SmartModel {
|
||||||
|
date: string;
|
||||||
|
device_wwn: string;
|
||||||
|
device_protocol: string;
|
||||||
|
|
||||||
|
temp: number;
|
||||||
|
power_on_hours: number;
|
||||||
|
power_cycle_count: number
|
||||||
|
attrs: { [key: string]: SmartAttributeModel }
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
// maps to webapp/backend/pkg/models/measurements/smart_temperature.go
|
||||||
|
export interface SmartTemperatureModel {
|
||||||
|
date: string;
|
||||||
|
temp: number;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
// map to webapp/backend/pkg/thresholds/ata_attribute_metadata.go
|
||||||
|
// map to webapp/backend/pkg/thresholds/nvme_attribute_metadata.go
|
||||||
|
// map to webapp/backend/pkg/thresholds/scsi_attribute_metadata.go
|
||||||
|
export interface AttributeMetadataModel {
|
||||||
|
display_name: string
|
||||||
|
ideal: string
|
||||||
|
critical: boolean
|
||||||
|
description: string
|
||||||
|
|
||||||
|
transform_value_unit?: string
|
||||||
|
observed_thresholds?: any[]
|
||||||
|
display_type: string
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,64 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {DashboardDeviceDeleteDialogComponent} from './dashboard-device-delete-dialog.component';
|
||||||
|
import {HttpClientModule} from '@angular/common/http';
|
||||||
|
import {MAT_DIALOG_DATA, MatDialogModule, MatDialogRef} from '@angular/material/dialog';
|
||||||
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
|
import {SharedModule} from '../../../shared/shared.module';
|
||||||
|
import {DashboardDeviceDeleteDialogService} from './dashboard-device-delete-dialog.service';
|
||||||
|
import {of} from 'rxjs';
|
||||||
|
|
||||||
import { DashboardDeviceDeleteDialogComponent } from './dashboard-device-delete-dialog.component';
|
|
||||||
|
|
||||||
describe('DashboardDeviceDeleteDialogComponent', () => {
|
describe('DashboardDeviceDeleteDialogComponent', () => {
|
||||||
let component: DashboardDeviceDeleteDialogComponent;
|
let component: DashboardDeviceDeleteDialogComponent;
|
||||||
let fixture: ComponentFixture<DashboardDeviceDeleteDialogComponent>;
|
let fixture: ComponentFixture<DashboardDeviceDeleteDialogComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
const matDialogRefSpy = jasmine.createSpyObj('MatDialogRef', ['closeDialog', 'close']);
|
||||||
TestBed.configureTestingModule({
|
const dashboardDeviceDeleteDialogServiceSpy = jasmine.createSpyObj('DashboardDeviceDeleteDialogService', ['deleteDevice']);
|
||||||
declarations: [ DashboardDeviceDeleteDialogComponent ]
|
|
||||||
})
|
beforeEach(async(() => {
|
||||||
.compileComponents();
|
TestBed.configureTestingModule({
|
||||||
}));
|
imports: [
|
||||||
|
HttpClientModule,
|
||||||
beforeEach(() => {
|
MatDialogModule,
|
||||||
fixture = TestBed.createComponent(DashboardDeviceDeleteDialogComponent);
|
MatButtonModule,
|
||||||
component = fixture.componentInstance;
|
MatIconModule,
|
||||||
fixture.detectChanges();
|
SharedModule,
|
||||||
});
|
],
|
||||||
|
providers: [
|
||||||
it('should create', () => {
|
{provide: MatDialogRef, useValue: matDialogRefSpy},
|
||||||
expect(component).toBeTruthy();
|
{provide: MAT_DIALOG_DATA, useValue: {wwn: 'test-wwn', title: 'my-test-device-title'}},
|
||||||
});
|
{provide: DashboardDeviceDeleteDialogService, useValue: dashboardDeviceDeleteDialogServiceSpy}
|
||||||
|
],
|
||||||
|
declarations: [DashboardDeviceDeleteDialogComponent]
|
||||||
|
})
|
||||||
|
.compileComponents()
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DashboardDeviceDeleteDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should close the component if cancel is clicked', () => {
|
||||||
|
matDialogRefSpy.closeDialog.calls.reset();
|
||||||
|
matDialogRefSpy.closeDialog()
|
||||||
|
expect(matDialogRefSpy.closeDialog).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should attempt to delete device if delete is clicked', () => {
|
||||||
|
dashboardDeviceDeleteDialogServiceSpy.deleteDevice.and.returnValue(of({'success': true}));
|
||||||
|
|
||||||
|
component.onDeleteClick()
|
||||||
|
expect(dashboardDeviceDeleteDialogServiceSpy.deleteDevice).toHaveBeenCalledWith('test-wwn');
|
||||||
|
expect(dashboardDeviceDeleteDialogServiceSpy.deleteDevice.calls.count())
|
||||||
|
.withContext('one call')
|
||||||
|
.toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,25 +1,105 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
import { DashboardDeviceComponent } from './dashboard-device.component';
|
import {DashboardDeviceComponent} from './dashboard-device.component';
|
||||||
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
|
import {SharedModule} from 'app/shared/shared.module';
|
||||||
|
import {MatMenuModule} from '@angular/material/menu';
|
||||||
|
import {TREO_APP_CONFIG} from '@treo/services/config/config.constants';
|
||||||
|
import {DeviceSummaryModel} from 'app/core/models/device-summary-model';
|
||||||
|
import * as moment from 'moment';
|
||||||
|
|
||||||
describe('DashboardDeviceComponent', () => {
|
describe('DashboardDeviceComponent', () => {
|
||||||
let component: DashboardDeviceComponent;
|
let component: DashboardDeviceComponent;
|
||||||
let fixture: ComponentFixture<DashboardDeviceComponent>;
|
let fixture: ComponentFixture<DashboardDeviceComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
const matDialogSpy = jasmine.createSpyObj('MatDialog', ['open']);
|
||||||
TestBed.configureTestingModule({
|
// const configServiceSpy = jasmine.createSpyObj('TreoConfigService', ['config$']);
|
||||||
declarations: [ DashboardDeviceComponent ]
|
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatMenuModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{provide: MatDialog, useValue: matDialogSpy},
|
||||||
|
{provide: TREO_APP_CONFIG, useValue: {dashboardDisplay: 'name'}}
|
||||||
|
],
|
||||||
|
declarations: [DashboardDeviceComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// configServiceSpy.config$.and.returnValue(of({'success': true}));
|
||||||
|
fixture = TestBed.createComponent(DashboardDeviceComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#classDeviceLastUpdatedOn()', () => {
|
||||||
|
|
||||||
|
it('if non-zero device status, should be red', () => {
|
||||||
|
// component.deviceSummary = summary.data.summary['0x5000c500673e6b5f'] as DeviceSummaryModel
|
||||||
|
expect(component.classDeviceLastUpdatedOn({
|
||||||
|
device: {
|
||||||
|
device_status: 2
|
||||||
|
}
|
||||||
|
} as DeviceSummaryModel)).toBe('text-red')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if non-zero device status, should be red', () => {
|
||||||
|
// component.deviceSummary = summary.data.summary['0x5000c500673e6b5f'] as DeviceSummaryModel
|
||||||
|
expect(component.classDeviceLastUpdatedOn({
|
||||||
|
device: {
|
||||||
|
device_status: 2
|
||||||
|
}
|
||||||
|
} as DeviceSummaryModel)).toBe('text-red')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if healthy device status and updated in the last two weeks, should be green', () => {
|
||||||
|
// component.deviceSummary = summary.data.summary['0x5000c500673e6b5f'] as DeviceSummaryModel
|
||||||
|
expect(component.classDeviceLastUpdatedOn({
|
||||||
|
device: {
|
||||||
|
device_status: 0
|
||||||
|
},
|
||||||
|
smart: {
|
||||||
|
collector_date: moment().subtract(13, 'days').toISOString()
|
||||||
|
}
|
||||||
|
} as DeviceSummaryModel)).toBe('text-green')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if healthy device status and updated more than two weeks ago, but less than 1 month, should be yellow', () => {
|
||||||
|
// component.deviceSummary = summary.data.summary['0x5000c500673e6b5f'] as DeviceSummaryModel
|
||||||
|
expect(component.classDeviceLastUpdatedOn({
|
||||||
|
device: {
|
||||||
|
device_status: 0
|
||||||
|
},
|
||||||
|
smart: {
|
||||||
|
collector_date: moment().subtract(3, 'weeks').toISOString()
|
||||||
|
}
|
||||||
|
} as DeviceSummaryModel)).toBe('text-yellow')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if healthy device status and updated more 1 month ago, should be red', () => {
|
||||||
|
// component.deviceSummary = summary.data.summary['0x5000c500673e6b5f'] as DeviceSummaryModel
|
||||||
|
expect(component.classDeviceLastUpdatedOn({
|
||||||
|
device: {
|
||||||
|
device_status: 0
|
||||||
|
},
|
||||||
|
smart: {
|
||||||
|
collector_date: moment().subtract(5, 'weeks').toISOString()
|
||||||
|
}
|
||||||
|
} as DeviceSummaryModel)).toBe('text-red')
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(DashboardDeviceComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,53 +1,30 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import { RouterModule } from '@angular/router';
|
import {RouterModule} from '@angular/router';
|
||||||
import { Overlay } from '@angular/cdk/overlay';
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MatAutocompleteModule } from '@angular/material/autocomplete';
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import {SharedModule} from 'app/shared/shared.module';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatInputModule } from '@angular/material/input';
|
|
||||||
import { SharedModule } from 'app/shared/shared.module';
|
|
||||||
import {DashboardDeviceComponent} from 'app/layout/common/dashboard-device/dashboard-device.component'
|
import {DashboardDeviceComponent} from 'app/layout/common/dashboard-device/dashboard-device.component'
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
|
||||||
import { MatButtonToggleModule} from '@angular/material/button-toggle';
|
|
||||||
import {MatTabsModule} from '@angular/material/tabs';
|
|
||||||
import {MatSliderModule} from '@angular/material/slider';
|
|
||||||
import {MatSlideToggleModule} from '@angular/material/slide-toggle';
|
|
||||||
import {MatTooltipModule} from '@angular/material/tooltip';
|
|
||||||
import {dashboardRoutes} from '../../../modules/dashboard/dashboard.routing';
|
import {dashboardRoutes} from '../../../modules/dashboard/dashboard.routing';
|
||||||
import {MatDividerModule} from '@angular/material/divider';
|
|
||||||
import {MatMenuModule} from '@angular/material/menu';
|
import {MatMenuModule} from '@angular/material/menu';
|
||||||
import {MatProgressBarModule} from '@angular/material/progress-bar';
|
|
||||||
import {MatSortModule} from '@angular/material/sort';
|
|
||||||
import {MatTableModule} from '@angular/material/table';
|
|
||||||
import {NgApexchartsModule} from 'ng-apexcharts';
|
|
||||||
import {DashboardDeviceDeleteDialogModule} from 'app/layout/common/dashboard-device-delete-dialog/dashboard-device-delete-dialog.module';
|
import {DashboardDeviceDeleteDialogModule} from 'app/layout/common/dashboard-device-delete-dialog/dashboard-device-delete-dialog.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
DashboardDeviceComponent
|
DashboardDeviceComponent
|
||||||
],
|
],
|
||||||
imports : [
|
imports: [
|
||||||
RouterModule.forChild([]),
|
RouterModule.forChild([]),
|
||||||
RouterModule.forChild(dashboardRoutes),
|
RouterModule.forChild(dashboardRoutes),
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDividerModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatProgressBarModule,
|
|
||||||
MatSortModule,
|
|
||||||
MatTableModule,
|
|
||||||
NgApexchartsModule,
|
|
||||||
SharedModule,
|
SharedModule,
|
||||||
DashboardDeviceDeleteDialogModule
|
DashboardDeviceDeleteDialogModule
|
||||||
],
|
],
|
||||||
exports : [
|
exports: [
|
||||||
DashboardDeviceComponent,
|
DashboardDeviceComponent,
|
||||||
],
|
],
|
||||||
providers : []
|
providers: []
|
||||||
})
|
})
|
||||||
export class DashboardDeviceModule
|
export class DashboardDeviceModule {
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { DashboardSettingsComponent } from './dashboard-settings.component';
|
|
||||||
|
|
||||||
describe('DashboardSettingsComponent', () => {
|
|
||||||
let component: DashboardSettingsComponent;
|
|
||||||
let fixture: ComponentFixture<DashboardSettingsComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ DashboardSettingsComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(DashboardSettingsComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,44 @@
|
|||||||
|
import {HttpClient} from '@angular/common/http';
|
||||||
|
import {DashboardService} from './dashboard.service';
|
||||||
|
import {of} from 'rxjs';
|
||||||
|
import {summary} from 'app/data/mock/summary/data'
|
||||||
|
import {temp_history} from 'app/data/mock/summary/temp_history'
|
||||||
|
import {DeviceSummaryModel} from 'app/core/models/device-summary-model';
|
||||||
|
import {SmartTemperatureModel} from 'app/core/models/measurements/smart-temperature-model';
|
||||||
|
|
||||||
|
describe('DashboardService', () => {
|
||||||
|
let service: DashboardService;
|
||||||
|
let httpClientSpy: jasmine.SpyObj<HttpClient>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
|
||||||
|
service = new DashboardService(httpClientSpy);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unwrap and return getSummaryData() (HttpClient called once)', (done: DoneFn) => {
|
||||||
|
httpClientSpy.get.and.returnValue(of(summary));
|
||||||
|
|
||||||
|
service.getSummaryData().subscribe(value => {
|
||||||
|
expect(value).toBe(summary.data.summary as { [key: string]: DeviceSummaryModel });
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
expect(httpClientSpy.get.calls.count())
|
||||||
|
.withContext('one call')
|
||||||
|
.toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should unwrap and return getSummaryTempData() (HttpClient called once)', (done: DoneFn) => {
|
||||||
|
// const expectedHeroes: any[] =
|
||||||
|
// [{ id: 1, name: 'A' }, { id: 2, name: 'B' }];
|
||||||
|
|
||||||
|
httpClientSpy.get.and.returnValue(of(temp_history));
|
||||||
|
|
||||||
|
service.getSummaryTempData('weekly').subscribe(value => {
|
||||||
|
expect(value).toBe(temp_history.data.temp_history as { [key: string]: SmartTemperatureModel[] });
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
expect(httpClientSpy.get.calls.count())
|
||||||
|
.withContext('one call')
|
||||||
|
.toBe(1);
|
||||||
|
});
|
||||||
|
});
|
@ -1,25 +0,0 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { DetailComponent } from './detail.component';
|
|
||||||
|
|
||||||
describe('DetailComponent', () => {
|
|
||||||
let component: DetailComponent;
|
|
||||||
let fixture: ComponentFixture<DetailComponent>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
declarations: [ DetailComponent ]
|
|
||||||
})
|
|
||||||
.compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(DetailComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
@ -0,0 +1,28 @@
|
|||||||
|
import {HttpClient} from '@angular/common/http';
|
||||||
|
import {DetailService} from './detail.service';
|
||||||
|
import {of} from 'rxjs';
|
||||||
|
import {sda} from 'app/data/mock/device/details/sda'
|
||||||
|
import {DeviceDetailsResponseWrapper} from 'app/core/models/device-details-response-wrapper';
|
||||||
|
|
||||||
|
describe('DetailService', () => {
|
||||||
|
describe('#getData', () => {
|
||||||
|
let service: DetailService;
|
||||||
|
let httpClientSpy: jasmine.SpyObj<HttpClient>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']);
|
||||||
|
service = new DetailService(httpClientSpy);
|
||||||
|
});
|
||||||
|
it('should return getData() (HttpClient called once)', (done: DoneFn) => {
|
||||||
|
httpClientSpy.get.and.returnValue(of(sda));
|
||||||
|
|
||||||
|
service.getData('test').subscribe(value => {
|
||||||
|
expect(value).toBe(sda as DeviceDetailsResponseWrapper);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
expect(httpClientSpy.get.calls.count())
|
||||||
|
.withContext('one call')
|
||||||
|
.toBe(1);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
@ -1,8 +1,151 @@
|
|||||||
import { DeviceTitlePipe } from './device-title.pipe';
|
import {DeviceTitlePipe} from './device-title.pipe';
|
||||||
|
import {DeviceModel} from 'app/core/models/device-model';
|
||||||
|
|
||||||
describe('DeviceTitlePipe', () => {
|
describe('DeviceTitlePipe', () => {
|
||||||
it('create an instance', () => {
|
it('create an instance', () => {
|
||||||
const pipe = new DeviceTitlePipe();
|
const pipe = new DeviceTitlePipe();
|
||||||
expect(pipe).toBeTruthy();
|
expect(pipe).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#deviceTitleForType', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'sda',
|
||||||
|
'device_type': 'ata',
|
||||||
|
'model_name': 'Samsung',
|
||||||
|
},
|
||||||
|
'titleType': 'name',
|
||||||
|
'result': '/dev/sda - Samsung'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'nvme0',
|
||||||
|
'device_type': 'nvme',
|
||||||
|
'model_name': 'Samsung',
|
||||||
|
},
|
||||||
|
'titleType': 'name',
|
||||||
|
'result': '/dev/nvme0 - nvme - Samsung'
|
||||||
|
},{
|
||||||
|
'device': {},
|
||||||
|
'titleType': 'serial_id',
|
||||||
|
'result': ''
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_serial_id': 'ata-WDC_WD140EDFZ-11AXXXXX_9RXXXXXX',
|
||||||
|
},
|
||||||
|
'titleType': 'serial_id',
|
||||||
|
'result': '/by-id/ata-WDC_WD140EDFZ-11AXXXXX_9RXXXXXX'
|
||||||
|
},{
|
||||||
|
'device': {},
|
||||||
|
'titleType': 'uuid',
|
||||||
|
'result': ''
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_uuid': 'abcdef-1234-4567-8901'
|
||||||
|
},
|
||||||
|
'titleType': 'uuid',
|
||||||
|
'result': '/by-uuid/abcdef-1234-4567-8901'
|
||||||
|
},{
|
||||||
|
'device': {},
|
||||||
|
'titleType': 'label',
|
||||||
|
'result': ''
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'label': 'custom-device-label'
|
||||||
|
},
|
||||||
|
'titleType': 'label',
|
||||||
|
'result': 'custom-device-label'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_label': 'drive-volume-label'
|
||||||
|
},
|
||||||
|
'titleType': 'label',
|
||||||
|
'result': '/by-label/drive-volume-label'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
testCases.forEach((test, index) => {
|
||||||
|
it(`should correctly format device title ${JSON.stringify(test.device)}. (testcase: ${index + 1})`, () => {
|
||||||
|
// test
|
||||||
|
const formatted = DeviceTitlePipe.deviceTitleForType(test.device as DeviceModel, test.titleType)
|
||||||
|
expect(formatted).toEqual(test.result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#deviceTitleWithFallback',() => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'sda',
|
||||||
|
'device_type': 'ata',
|
||||||
|
'model_name': 'Samsung',
|
||||||
|
},
|
||||||
|
'titleType': 'name',
|
||||||
|
'result': '/dev/sda - Samsung'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'nvme0',
|
||||||
|
'device_type': 'nvme',
|
||||||
|
'model_name': 'Samsung',
|
||||||
|
},
|
||||||
|
'titleType': 'name',
|
||||||
|
'result': '/dev/nvme0 - nvme - Samsung'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'fallback',
|
||||||
|
'device_type': 'ata',
|
||||||
|
'model_name': 'fallback',
|
||||||
|
},
|
||||||
|
'titleType': 'serial_id',
|
||||||
|
'result': '/dev/fallback - fallback'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_serial_id': 'ata-WDC_WD140EDFZ-11AXXXXX_9RXXXXXX',
|
||||||
|
},
|
||||||
|
'titleType': 'serial_id',
|
||||||
|
'result': '/by-id/ata-WDC_WD140EDFZ-11AXXXXX_9RXXXXXX'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'fallback',
|
||||||
|
'device_type': 'ata',
|
||||||
|
'model_name': 'fallback',
|
||||||
|
},
|
||||||
|
'titleType': 'uuid',
|
||||||
|
'result': '/dev/fallback - fallback'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_uuid': 'abcdef-1234-4567-8901'
|
||||||
|
},
|
||||||
|
'titleType': 'uuid',
|
||||||
|
'result': '/by-uuid/abcdef-1234-4567-8901'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_name': 'fallback',
|
||||||
|
'device_type': 'ata',
|
||||||
|
'model_name': 'fallback',
|
||||||
|
},
|
||||||
|
'titleType': 'label',
|
||||||
|
'result': '/dev/fallback - fallback'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'label': 'custom-device-label'
|
||||||
|
},
|
||||||
|
'titleType': 'label',
|
||||||
|
'result': 'custom-device-label'
|
||||||
|
},{
|
||||||
|
'device': {
|
||||||
|
'device_label': 'drive-volume-label'
|
||||||
|
},
|
||||||
|
'titleType': 'label',
|
||||||
|
'result': '/by-label/drive-volume-label'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
testCases.forEach((test, index) => {
|
||||||
|
it(`should correctly format device title ${JSON.stringify(test.device)}. (testcase: ${index + 1})`, () => {
|
||||||
|
// test
|
||||||
|
const formatted = DeviceTitlePipe.deviceTitleWithFallback(test.device as DeviceModel, test.titleType)
|
||||||
|
expect(formatted).toEqual(test.result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,35 @@
|
|||||||
|
import { FileSizePipe } from './file-size.pipe';
|
||||||
|
|
||||||
|
describe('FileSizePipe', () => {
|
||||||
|
it('create an instance', () => {
|
||||||
|
const pipe = new FileSizePipe();
|
||||||
|
expect(pipe).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#transform',() => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
'bytes': 1500,
|
||||||
|
'precision': undefined,
|
||||||
|
'result': '1 KB'
|
||||||
|
},{
|
||||||
|
'bytes': 2_100_000_000,
|
||||||
|
'precision': undefined,
|
||||||
|
'result': '2.0 GB',
|
||||||
|
},{
|
||||||
|
'bytes': 1500,
|
||||||
|
'precision': 2,
|
||||||
|
'result': '1.46 KB',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testCases.forEach((test, index) => {
|
||||||
|
it(`should correctly format bytes ${test.bytes}. (testcase: ${index + 1})`, () => {
|
||||||
|
// test
|
||||||
|
const pipe = new FileSizePipe();
|
||||||
|
const formatted = pipe.transform(test.bytes, test.precision)
|
||||||
|
expect(formatted).toEqual(test.result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
@ -1,8 +1,83 @@
|
|||||||
import { TemperaturePipe } from './temperature.pipe';
|
import { TemperaturePipe } from './temperature.pipe';
|
||||||
|
|
||||||
describe('TemperaturePipe', () => {
|
describe('TemperaturePipe', () => {
|
||||||
it('create an instance', () => {
|
it('create an instance', () => {
|
||||||
const pipe = new TemperaturePipe();
|
const pipe = new TemperaturePipe();
|
||||||
expect(pipe).toBeTruthy();
|
expect(pipe).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('#celsiusToFahrenheit', () => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
'c': -273.15,
|
||||||
|
'f': -460,
|
||||||
|
},{
|
||||||
|
'c': -34.44,
|
||||||
|
'f': -30,
|
||||||
|
},{
|
||||||
|
'c': -23.33,
|
||||||
|
'f': -10,
|
||||||
|
},{
|
||||||
|
'c': -17.78,
|
||||||
|
'f': -0,
|
||||||
|
},{
|
||||||
|
'c': 0,
|
||||||
|
'f': 32,
|
||||||
|
},{
|
||||||
|
'c': 10,
|
||||||
|
'f': 50,
|
||||||
|
},{
|
||||||
|
'c': 26.67,
|
||||||
|
'f': 80,
|
||||||
|
},{
|
||||||
|
'c': 37,
|
||||||
|
'f': 99,
|
||||||
|
},{
|
||||||
|
'c': 60,
|
||||||
|
'f': 140,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testCases.forEach((test, index) => {
|
||||||
|
it(`should correctly convert ${test.c}, Celsius to Fahrenheit (testcase: ${index + 1})`, () => {
|
||||||
|
// test
|
||||||
|
const numb = TemperaturePipe.celsiusToFahrenheit(test.c)
|
||||||
|
const roundNumb = Math.round(numb);
|
||||||
|
expect(roundNumb).toEqual(test.f);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#formatTemperature',() => {
|
||||||
|
const testCases = [
|
||||||
|
{
|
||||||
|
'c': 26.67,
|
||||||
|
'unit': 'celsius',
|
||||||
|
'includeUnits': true,
|
||||||
|
'result': '26.67°C'
|
||||||
|
},{
|
||||||
|
'c': 26.67,
|
||||||
|
'unit': 'celsius',
|
||||||
|
'includeUnits': false,
|
||||||
|
'result': '26.67',
|
||||||
|
},{
|
||||||
|
'c': 26.67,
|
||||||
|
'unit': 'fahrenheit',
|
||||||
|
'includeUnits': true,
|
||||||
|
'result': '80.006°F',
|
||||||
|
},{
|
||||||
|
'c': 26.67,
|
||||||
|
'unit': 'fahrenheit',
|
||||||
|
'includeUnits': false,
|
||||||
|
'result': '80.006',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
testCases.forEach((test, index) => {
|
||||||
|
it(`should correctly format temperature ${test.c} to ${test.unit} ${test.includeUnits ? 'with' : 'without'} unit. (testcase: ${index + 1})`, () => {
|
||||||
|
// test
|
||||||
|
const formatted = TemperaturePipe.formatTemperature(test.c, test.unit, test.includeUnits)
|
||||||
|
expect(formatted).toEqual(test.result);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in new issue