@@ -13,7 +13,7 @@ func DevicePrefix() string {
|
|||||||
|
|
||||||
func (d *Detect) Start() ([]models.Device, error) {
|
func (d *Detect) Start() ([]models.Device, error) {
|
||||||
d.Shell = shell.Create()
|
d.Shell = shell.Create()
|
||||||
// call the base/common functionality to get a list of devicess
|
// call the base/common functionality to get a list of devices
|
||||||
detectedDevices, err := d.SmartctlScan()
|
detectedDevices, err := d.SmartctlScan()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package detect
|
package detect
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/common/shell"
|
"github.com/analogj/scrutiny/collector/pkg/common/shell"
|
||||||
"github.com/analogj/scrutiny/collector/pkg/models"
|
"github.com/analogj/scrutiny/collector/pkg/models"
|
||||||
"github.com/jaypipes/ghw"
|
"github.com/jaypipes/ghw"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +25,7 @@ func (d *Detect) Start() ([]models.Device, error) {
|
|||||||
//inflate device info for detected devices.
|
//inflate device info for detected devices.
|
||||||
for ndx, _ := range detectedDevices {
|
for ndx, _ := range detectedDevices {
|
||||||
d.SmartCtlInfo(&detectedDevices[ndx]) //ignore errors.
|
d.SmartCtlInfo(&detectedDevices[ndx]) //ignore errors.
|
||||||
|
populateUdevInfo(&detectedDevices[ndx]) //ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
return detectedDevices, nil
|
return detectedDevices, nil
|
||||||
@@ -49,3 +53,51 @@ func (d *Detect) wwnFallback(detectedDevice *models.Device) {
|
|||||||
//wwn must always be lowercase.
|
//wwn must always be lowercase.
|
||||||
detectedDevice.WWN = strings.ToLower(detectedDevice.WWN)
|
detectedDevice.WWN = strings.ToLower(detectedDevice.WWN)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// as discussed in
|
||||||
|
// - https://github.com/AnalogJ/scrutiny/issues/225
|
||||||
|
// - https://github.com/jaypipes/ghw/issues/59#issue-361915216
|
||||||
|
// udev exposes its data in a standardized way under /run/udev/data/....
|
||||||
|
func populateUdevInfo(detectedDevice *models.Device) error {
|
||||||
|
// Get device major:minor numbers
|
||||||
|
// `cat /sys/class/block/sda/dev`
|
||||||
|
devNo, err := ioutil.ReadFile(filepath.Join("/sys/class/block/", detectedDevice.DeviceName, "dev"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up block device in udev runtime database
|
||||||
|
// `cat /run/udev/data/b8:0`
|
||||||
|
udevID := "b" + strings.TrimSpace(string(devNo))
|
||||||
|
udevBytes, err := ioutil.ReadFile(filepath.Join("/run/udev/data/", udevID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceMountPaths := []string{}
|
||||||
|
udevInfo := make(map[string]string)
|
||||||
|
for _, udevLine := range strings.Split(string(udevBytes), "\n") {
|
||||||
|
if strings.HasPrefix(udevLine, "E:") {
|
||||||
|
if s := strings.SplitN(udevLine[2:], "=", 2); len(s) == 2 {
|
||||||
|
udevInfo[s[0]] = s[1]
|
||||||
|
}
|
||||||
|
} else if strings.HasPrefix(udevLine, "S:") {
|
||||||
|
deviceMountPaths = append(deviceMountPaths, udevLine[2:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set additional device information.
|
||||||
|
if deviceLabel, exists := udevInfo["ID_FS_LABEL"]; exists {
|
||||||
|
detectedDevice.DeviceLabel = deviceLabel
|
||||||
|
}
|
||||||
|
if deviceUUID, exists := udevInfo["ID_FS_UUID"]; exists {
|
||||||
|
detectedDevice.DeviceUUID = deviceUUID
|
||||||
|
}
|
||||||
|
if deviceSerialID, exists := udevInfo["ID_SERIAL"]; exists {
|
||||||
|
detectedDevice.DeviceSerialID = fmt.Sprintf("%s-%s", udevInfo["ID_BUS"], deviceSerialID)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
WWN string `json:"wwn"`
|
WWN string `json:"wwn"`
|
||||||
HostId string `json:"host_id"`
|
|
||||||
|
|
||||||
DeviceName string `json:"device_name"`
|
DeviceName string `json:"device_name"`
|
||||||
|
DeviceUUID string `json:"device_uuid"`
|
||||||
|
DeviceSerialID string `json:"device_serial_id"`
|
||||||
|
DeviceLabel string `json:"device_label"`
|
||||||
|
|
||||||
Manufacturer string `json:"manufacturer"`
|
Manufacturer string `json:"manufacturer"`
|
||||||
ModelName string `json:"model_name"`
|
ModelName string `json:"model_name"`
|
||||||
InterfaceType string `json:"interface_type"`
|
InterfaceType string `json:"interface_type"`
|
||||||
@@ -17,6 +20,10 @@ type Device struct {
|
|||||||
SmartSupport bool `json:"smart_support"`
|
SmartSupport bool `json:"smart_support"`
|
||||||
DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI)
|
DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI)
|
||||||
DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector.
|
DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector.
|
||||||
|
|
||||||
|
// User provided metadata
|
||||||
|
Label string `json:"label"`
|
||||||
|
HostId string `json:"host_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeviceWrapper struct {
|
type DeviceWrapper struct {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Deprecated: m20220503120000.Device is deprecated, only used by db migrations
|
||||||
type Device struct {
|
type Device struct {
|
||||||
//GORM attributes, see: http://gorm.io/docs/conventions.html
|
//GORM attributes, see: http://gorm.io/docs/conventions.html
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package m20220509170100
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/analogj/scrutiny/webapp/backend/pkg"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
//GORM attributes, see: http://gorm.io/docs/conventions.html
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt *time.Time
|
||||||
|
|
||||||
|
WWN string `json:"wwn" gorm:"primary_key"`
|
||||||
|
|
||||||
|
DeviceName string `json:"device_name"`
|
||||||
|
DeviceUUID string `json:"device_uuid"`
|
||||||
|
DeviceSerialID string `json:"device_serial_id"`
|
||||||
|
DeviceLabel string `json:"device_label"`
|
||||||
|
|
||||||
|
Manufacturer string `json:"manufacturer"`
|
||||||
|
ModelName string `json:"model_name"`
|
||||||
|
InterfaceType string `json:"interface_type"`
|
||||||
|
InterfaceSpeed string `json:"interface_speed"`
|
||||||
|
SerialNumber string `json:"serial_number"`
|
||||||
|
Firmware string `json:"firmware"`
|
||||||
|
RotationSpeed int `json:"rotational_speed"`
|
||||||
|
Capacity int64 `json:"capacity"`
|
||||||
|
FormFactor string `json:"form_factor"`
|
||||||
|
SmartSupport bool `json:"smart_support"`
|
||||||
|
DeviceProtocol string `json:"device_protocol"` //protocol determines which smart attribute types are available (ATA, NVMe, SCSI)
|
||||||
|
DeviceType string `json:"device_type"` //device type is used for querying with -d/t flag, should only be used by collector.
|
||||||
|
|
||||||
|
// User provided metadata
|
||||||
|
Label string `json:"label"`
|
||||||
|
HostId string `json:"host_id"`
|
||||||
|
|
||||||
|
// Data set by Scrutiny
|
||||||
|
DeviceStatus pkg.DeviceStatus `json:"device_status"`
|
||||||
|
}
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
func (sr *scrutinyRepository) RegisterDevice(ctx context.Context, dev models.Device) error {
|
func (sr *scrutinyRepository) RegisterDevice(ctx context.Context, dev models.Device) error {
|
||||||
if err := sr.gormClient.WithContext(ctx).Clauses(clause.OnConflict{
|
if err := sr.gormClient.WithContext(ctx).Clauses(clause.OnConflict{
|
||||||
Columns: []clause.Column{{Name: "wwn"}},
|
Columns: []clause.Column{{Name: "wwn"}},
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"host_id", "device_name", "device_type"}),
|
DoUpdates: clause.AssignmentColumns([]string{"host_id", "device_name", "device_type", "device_uuid", "device_serial_id", "device_label"}),
|
||||||
}).Create(&dev).Error; err != nil {
|
}).Create(&dev).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20201107210306"
|
"github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20201107210306"
|
||||||
"github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220503120000"
|
"github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220503120000"
|
||||||
|
"github.com/analogj/scrutiny/webapp/backend/pkg/database/migrations/m20220509170100"
|
||||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models"
|
"github.com/analogj/scrutiny/webapp/backend/pkg/models"
|
||||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
"github.com/analogj/scrutiny/webapp/backend/pkg/models/collector"
|
||||||
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
"github.com/analogj/scrutiny/webapp/backend/pkg/models/measurements"
|
||||||
@@ -253,10 +254,19 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
//migrate the device database to the current version
|
//migrate the device database
|
||||||
return tx.AutoMigrate(m20220503120000.Device{})
|
return tx.AutoMigrate(m20220503120000.Device{})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
ID: "m20220509170100", // addl udev device data
|
||||||
|
Migrate: func(tx *gorm.DB) error {
|
||||||
|
|
||||||
|
//migrate the device database.
|
||||||
|
// adding addl columns (device_label, device_uuid, device_serial_id)
|
||||||
|
return tx.AutoMigrate(m20220509170100.Device{})
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := m.Migrate(); err != nil {
|
if err := m.Migrate(); err != nil {
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ 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"`
|
||||||
|
DeviceSerialID string `json:"device_serial_id"`
|
||||||
|
DeviceLabel string `json:"device_label"`
|
||||||
|
|
||||||
Manufacturer string `json:"manufacturer"`
|
Manufacturer string `json:"manufacturer"`
|
||||||
ModelName string `json:"model_name"`
|
ModelName string `json:"model_name"`
|
||||||
InterfaceType string `json:"interface_type"`
|
InterfaceType string `json:"interface_type"`
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ 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';
|
||||||
|
|
||||||
|
const SCRUTINY_CONFIG_LOCAL_STORAGE_KEY = 'scrutiny';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@@ -10,14 +12,22 @@ export class TreoConfigService
|
|||||||
{
|
{
|
||||||
// Private
|
// Private
|
||||||
private _config: BehaviorSubject<any>;
|
private _config: BehaviorSubject<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
constructor(@Inject(TREO_APP_CONFIG) config: any)
|
constructor(@Inject(TREO_APP_CONFIG) defaultConfig: any)
|
||||||
{
|
{
|
||||||
|
let currentScrutinyConfig = defaultConfig
|
||||||
|
|
||||||
|
let localConfigStr = localStorage.getItem(SCRUTINY_CONFIG_LOCAL_STORAGE_KEY)
|
||||||
|
if(localConfigStr){
|
||||||
|
//check localstorage for a value
|
||||||
|
let localConfig = JSON.parse(localConfigStr)
|
||||||
|
currentScrutinyConfig = localConfig
|
||||||
|
}
|
||||||
|
|
||||||
// Set the private defaults
|
// Set the private defaults
|
||||||
this._config = new BehaviorSubject(config);
|
this._config = new BehaviorSubject(currentScrutinyConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
@@ -27,15 +37,20 @@ export class TreoConfigService
|
|||||||
/**
|
/**
|
||||||
* Setter and getter for config
|
* Setter and getter for config
|
||||||
*/
|
*/
|
||||||
|
//Setter
|
||||||
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);
|
const config = _.merge({}, this._config.getValue(), value);
|
||||||
|
|
||||||
|
//Store the config in localstorage
|
||||||
|
localStorage.setItem(SCRUTINY_CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
|
||||||
|
|
||||||
// Execute the observable
|
// Execute the observable
|
||||||
this._config.next(config);
|
this._config.next(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Getter
|
||||||
get config$(): Observable<any>
|
get config$(): Observable<any>
|
||||||
{
|
{
|
||||||
return this._config.asObservable();
|
return this._config.asObservable();
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ export interface AppConfig
|
|||||||
{
|
{
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
layout: Layout;
|
layout: Layout;
|
||||||
|
|
||||||
|
// Dashboard options
|
||||||
|
dashboardDisplay: string;
|
||||||
|
dashboardSort: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +28,9 @@ export interface AppConfig
|
|||||||
*/
|
*/
|
||||||
export const appConfig: AppConfig = {
|
export const appConfig: AppConfig = {
|
||||||
theme : "light",
|
theme : "light",
|
||||||
layout: "material"
|
layout: "material",
|
||||||
|
|
||||||
|
dashboardDisplay: "name",
|
||||||
|
dashboardSort: "status",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x5000c500673e6b5f",
|
"wwn": "0x5000c500673e6b5f",
|
||||||
"device_name": "sdg",
|
"device_name": "sdg",
|
||||||
|
"device_label": "14TB-WD-DRIVE2",
|
||||||
|
"device_uuid": "",
|
||||||
|
"device_serial_id": "ata-ST6000DX000-1H217Z-Z4DXXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "ST6000DX000-1H217Z",
|
"model_name": "ST6000DX000-1H217Z",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
@@ -35,6 +38,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x5000cca252c859cc",
|
"wwn": "0x5000cca252c859cc",
|
||||||
"device_name": "sdd",
|
"device_name": "sdd",
|
||||||
|
"device_label": "14TB-WD-DRIVE1",
|
||||||
|
"device_uuid": "806cf4bc-d160-4d96-8ee9-3ab7cf2a2e1f",
|
||||||
|
"device_serial_id": "ata-WDC_WD80EFAX-68LHPN0-7SGLXXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "WDC_WD80EFAX-68LHPN0",
|
"model_name": "WDC_WD80EFAX-68LHPN0",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
@@ -68,6 +74,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x5000cca264eb01d7",
|
"wwn": "0x5000cca264eb01d7",
|
||||||
"device_name": "sdb",
|
"device_name": "sdb",
|
||||||
|
"device_label": "14TB-WD-DRIVE5",
|
||||||
|
"device_uuid": "8125ec6d-a7e4-4950-ac84-72d6a4d67128",
|
||||||
|
"device_serial_id": "ata-WDC_WD140EDFZ-11A0VA0-9RK1XXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
@@ -101,6 +110,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x5000cca264ebc248",
|
"wwn": "0x5000cca264ebc248",
|
||||||
"device_name": "sde",
|
"device_name": "sde",
|
||||||
|
"device_label": "14TB-WD-DRIVE3",
|
||||||
|
"device_uuid": "9eb60cde-d6d0-4172-b520-b241a6a5477f",
|
||||||
|
"device_serial_id": "ata-WDC_WD140EDFZ-11A0VA0-9RK3XXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
@@ -125,6 +137,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x5000cca264ec3183",
|
"wwn": "0x5000cca264ec3183",
|
||||||
"device_name": "sdc",
|
"device_name": "sdc",
|
||||||
|
"device_label": "14TB-WD-DRIVE6",
|
||||||
|
"device_uuid": "e1378723-7861-49b9-8e01-0bd063f0ecdd",
|
||||||
|
"device_serial_id": "ata-WDC_WD140EDFZ-11A0VA0-9RK4XXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
"model_name": "WDC_WD140EDFZ-11A0VA0",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
@@ -138,7 +153,7 @@ export const summary = {
|
|||||||
"device_protocol": "",
|
"device_protocol": "",
|
||||||
"device_type": "",
|
"device_type": "",
|
||||||
"label": "",
|
"label": "",
|
||||||
"host_id": "",
|
"host_id": "custom host id",
|
||||||
"device_status": 1
|
"device_status": 1
|
||||||
},
|
},
|
||||||
"smart": {
|
"smart": {
|
||||||
@@ -542,6 +557,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x50014ee20b2a72a9",
|
"wwn": "0x50014ee20b2a72a9",
|
||||||
"device_name": "sdf",
|
"device_name": "sdf",
|
||||||
|
"device_label": "8.0TB-WD-4",
|
||||||
|
"device_uuid": "fc684dcc-aa2f-44f3-a958-d302dc7dd46d",
|
||||||
|
"device_serial_id": "ata-WDC_WD60EFRX-68MYMN1-WXL1HXXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "WDC_WD60EFRX-68MYMN1",
|
"model_name": "WDC_WD60EFRX-68MYMN1",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
@@ -566,6 +584,9 @@ export const summary = {
|
|||||||
"DeletedAt": null,
|
"DeletedAt": null,
|
||||||
"wwn": "0x5002538e40a22954",
|
"wwn": "0x5002538e40a22954",
|
||||||
"device_name": "sda",
|
"device_name": "sda",
|
||||||
|
"device_label": "",
|
||||||
|
"device_uuid": "",
|
||||||
|
"device_serial_id": "ata-Samsung_SSD_860_EVO_500GB-S3YZNB0KBXXXXXX",
|
||||||
"manufacturer": "ATA",
|
"manufacturer": "ATA",
|
||||||
"model_name": "Samsung_SSD_860_EVO_500GB",
|
"model_name": "Samsung_SSD_860_EVO_500GB",
|
||||||
"interface_type": "SCSI",
|
"interface_type": "SCSI",
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
<div [ngClass]="{ 'border-green': deviceSummary.device.device_status == 0 && deviceSummary.smart,
|
||||||
|
'border-red': deviceSummary.device.device_status != 0 }"
|
||||||
|
class="relative flex flex-col flex-auto p-6 pr-3 pb-3 bg-card rounded border-l-4 shadow-md overflow-hidden">
|
||||||
|
<div class="absolute bottom-0 right-0 w-24 h-24 -m-6">
|
||||||
|
<mat-icon class="icon-size-96 opacity-12 text-green"
|
||||||
|
*ngIf="deviceSummary.device.device_status == 0 && deviceSummary.smart"
|
||||||
|
[svgIcon]="'heroicons_outline:check-circle'"></mat-icon>
|
||||||
|
<mat-icon class="icon-size-96 opacity-12 text-red"
|
||||||
|
*ngIf="deviceSummary.device.device_status != 0"
|
||||||
|
[svgIcon]="'heroicons_outline:exclamation-circle'"></mat-icon>
|
||||||
|
<mat-icon class="icon-size-96 opacity-12 text-yellow"
|
||||||
|
*ngIf="!deviceSummary.smart"
|
||||||
|
[svgIcon]="'heroicons_outline:question-mark-circle'"></mat-icon>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<a [routerLink]="'/device/'+ deviceSummary.device.wwn"
|
||||||
|
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceTitle(deviceSummary.device)}}</a>
|
||||||
|
<div [ngClass]="classDeviceLastUpdatedOn(deviceSummary)" class="font-medium text-sm" *ngIf="deviceSummary.smart">
|
||||||
|
Last Updated on {{deviceSummary.smart.collector_date | date:'MMMM dd, yyyy - HH:mm' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ml-auto" *ngIf="deviceSummary.device">
|
||||||
|
<button mat-icon-button
|
||||||
|
[matMenuTriggerFor]="previousStatementMenu">
|
||||||
|
<mat-icon [svgIcon]="'more_vert'"></mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-menu #previousStatementMenu="matMenu">
|
||||||
|
<a mat-menu-item [routerLink]="'/device/'+ deviceSummary.device.wwn">
|
||||||
|
<span class="flex items-center">
|
||||||
|
<mat-icon class="icon-size-20 mr-3"
|
||||||
|
[svgIcon]="'payment'"></mat-icon>
|
||||||
|
<span>View Details</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</mat-menu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-row flex-wrap mt-4 -mx-6">
|
||||||
|
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
||||||
|
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Status</div>
|
||||||
|
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="deviceSummary.smart?.collector_date; else unknownStatus">{{ deviceStatusString(deviceSummary.device.device_status) | titlecase}}</div>
|
||||||
|
<ng-template #unknownStatus><div class="mt-2 font-medium text-3xl leading-none">No Data</div></ng-template>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
||||||
|
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Temperature</div>
|
||||||
|
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="deviceSummary.smart?.collector_date; else unknownTemp">{{ deviceSummary.smart?.temp }}°C</div>
|
||||||
|
<ng-template #unknownTemp><div class="mt-2 font-medium text-3xl leading-none">--</div></ng-template>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
||||||
|
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Capacity</div>
|
||||||
|
<div class="mt-2 font-medium text-3xl leading-none">{{ deviceSummary.device.capacity | fileSize}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
||||||
|
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Powered On</div>
|
||||||
|
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="deviceSummary.smart?.power_on_hours; else unknownPoweredOn">{{ humanizeDuration(deviceSummary.smart?.power_on_hours * 60 * 60 * 1000, { round: true, largest: 1, units: ['y', 'd', 'h'] }) }}</div>
|
||||||
|
<ng-template #unknownPoweredOn><div class="mt-2 font-medium text-3xl leading-none">--</div></ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DashboardDeviceComponent } from './dashboard-device.component';
|
||||||
|
|
||||||
|
describe('DashboardDeviceComponent', () => {
|
||||||
|
let component: DashboardDeviceComponent;
|
||||||
|
let fixture: ComponentFixture<DashboardDeviceComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ DashboardDeviceComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DashboardDeviceComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import * as moment from "moment";
|
||||||
|
import {takeUntil} from "rxjs/operators";
|
||||||
|
import {AppConfig} from "app/core/config/app.config";
|
||||||
|
import {TreoConfigService} from "@treo/services/config";
|
||||||
|
import {Subject} from "rxjs";
|
||||||
|
import humanizeDuration from 'humanize-duration'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-dashboard-device',
|
||||||
|
templateUrl: './dashboard-device.component.html',
|
||||||
|
styleUrls: ['./dashboard-device.component.scss']
|
||||||
|
})
|
||||||
|
export class DashboardDeviceComponent implements OnInit {
|
||||||
|
@Input() deviceSummary: any;
|
||||||
|
@Input() deviceWWN: string;
|
||||||
|
|
||||||
|
config: AppConfig;
|
||||||
|
|
||||||
|
private _unsubscribeAll: Subject<any>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _configService: TreoConfigService,
|
||||||
|
) {
|
||||||
|
// Set the private defaults
|
||||||
|
this._unsubscribeAll = new Subject();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Subscribe to config changes
|
||||||
|
this._configService.config$
|
||||||
|
.pipe(takeUntil(this._unsubscribeAll))
|
||||||
|
.subscribe((config: AppConfig) => {
|
||||||
|
this.config = config;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
// @ Public methods
|
||||||
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
classDeviceLastUpdatedOn(deviceSummary){
|
||||||
|
if (deviceSummary.device.device_status !== 0) {
|
||||||
|
return 'text-red' // if the device has failed, always highlight in red
|
||||||
|
} else if(deviceSummary.device.device_status === 0 && deviceSummary.smart){
|
||||||
|
if(moment().subtract(14, 'd').isBefore(deviceSummary.smart.collector_date)){
|
||||||
|
// this device was updated in the last 2 weeks.
|
||||||
|
return 'text-green'
|
||||||
|
} else if(moment().subtract(1, 'm').isBefore(deviceSummary.smart.collector_date)){
|
||||||
|
// this device was updated in the last month
|
||||||
|
return 'text-yellow'
|
||||||
|
} else{
|
||||||
|
// last updated more than a month ago.
|
||||||
|
return 'text-red'
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceTitle(disk){
|
||||||
|
|
||||||
|
console.log(`Displaying Device ${disk.wwn} with: ${this.config.dashboardDisplay}`)
|
||||||
|
let titleParts = []
|
||||||
|
if (disk.host_id) titleParts.push(disk.host_id)
|
||||||
|
|
||||||
|
//add device identifier (fallback to generated device name)
|
||||||
|
titleParts.push(deviceDisplayTitle(disk, this.config.dashboardDisplay) || deviceDisplayTitle(disk, 'name'))
|
||||||
|
|
||||||
|
return titleParts.join(' - ')
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceStatusString(deviceStatus){
|
||||||
|
if(deviceStatus == 0){
|
||||||
|
return "passed"
|
||||||
|
} else {
|
||||||
|
return "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly humanizeDuration = humanizeDuration;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deviceDisplayTitle(disk, titleType: string){
|
||||||
|
let titleParts = []
|
||||||
|
switch(titleType){
|
||||||
|
case 'name':
|
||||||
|
titleParts.push(`/dev/${disk.device_name}`)
|
||||||
|
if (disk.device_type && disk.device_type != 'scsi' && disk.device_type != 'ata'){
|
||||||
|
titleParts.push(disk.device_type)
|
||||||
|
}
|
||||||
|
titleParts.push(disk.model_name)
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'serial_id':
|
||||||
|
if(!disk.device_serial_id) return ''
|
||||||
|
titleParts.push(`/by-id/${disk.device_serial_id}`)
|
||||||
|
break;
|
||||||
|
case 'uuid':
|
||||||
|
if(!disk.device_uuid) return ''
|
||||||
|
titleParts.push(`/by-uuid/${disk.device_uuid}`)
|
||||||
|
break;
|
||||||
|
case 'label':
|
||||||
|
if(disk.label){
|
||||||
|
titleParts.push(disk.label)
|
||||||
|
} else if(disk.device_label){
|
||||||
|
titleParts.push(`/by-label/${disk.device_label}`)
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return titleParts.join(' - ')
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { Overlay } from '@angular/cdk/overlay';
|
||||||
|
import { MAT_AUTOCOMPLETE_SCROLL_STRATEGY, MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
|
import {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 {MatDividerModule} from "@angular/material/divider";
|
||||||
|
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 {DashboardSettingsModule} from "../dashboard-settings/dashboard-settings.module";
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
DashboardDeviceComponent
|
||||||
|
],
|
||||||
|
imports : [
|
||||||
|
RouterModule.forChild([]),
|
||||||
|
RouterModule.forChild(dashboardRoutes),
|
||||||
|
MatButtonModule,
|
||||||
|
MatDividerModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatProgressBarModule,
|
||||||
|
MatSortModule,
|
||||||
|
MatTableModule,
|
||||||
|
NgApexchartsModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
exports : [
|
||||||
|
DashboardDeviceComponent,
|
||||||
|
],
|
||||||
|
providers : []
|
||||||
|
})
|
||||||
|
export class DashboardDeviceModule
|
||||||
|
{
|
||||||
|
}
|
||||||
+30
-21
@@ -1,14 +1,23 @@
|
|||||||
<h2 mat-dialog-title>Scrutiny Settings</h2>
|
<h2 mat-dialog-title>Scrutiny Settings</h2>
|
||||||
<mat-dialog-content class="mat-typography">
|
<mat-dialog-content class="mat-typography">
|
||||||
|
|
||||||
<form 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 gt-xs:flex-row">
|
<div class="flex flex-col mt-5 gt-md:flex-row">
|
||||||
<mat-form-field class="flex-auto gt-xs:pr-3">
|
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pr-3">
|
||||||
|
<mat-label>Display Title</mat-label>
|
||||||
|
<mat-select [(ngModel)]="dashboardDisplay">
|
||||||
|
<mat-option value="name">Name</mat-option>
|
||||||
|
<mat-option value="serial_id">Serial ID</mat-option>
|
||||||
|
<mat-option value="uuid">UUID</mat-option>
|
||||||
|
<mat-option value="label">Label</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<mat-form-field class="flex-auto gt-xs:pr-3 gt-md:pl-3">
|
||||||
<mat-label>Sort By</mat-label>
|
<mat-label>Sort By</mat-label>
|
||||||
<mat-select [value]="'status'">
|
<mat-select [(ngModel)]="dashboardSort">
|
||||||
<mat-option value="status">Status</mat-option>
|
<mat-option value="status">Status</mat-option>
|
||||||
<mat-option value="name" disabled>Name</mat-option>
|
<mat-option value="title">Title</mat-option>
|
||||||
<mat-option value="label" disabled>Label</mat-option>
|
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
@@ -17,61 +26,61 @@
|
|||||||
<mat-tab-group mat-align-tabs="start">
|
<mat-tab-group mat-align-tabs="start">
|
||||||
<mat-tab label="Ata">
|
<mat-tab label="Ata">
|
||||||
|
|
||||||
<div class="flex flex-col mt-5 gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="gray-200 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>Critical Error Threshold</mat-label>
|
||||||
<input 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>Critical Warning Threshold</mat-label>
|
||||||
<input matInput>
|
<input disabled matInput>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="gray-200 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>Error Threshold</mat-label>
|
||||||
<input 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>Warning Threshold</mat-label>
|
||||||
<input matInput [value]="'10%'">
|
<input disabled matInput [value]="'10%'">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
<mat-tab label="NVMe">
|
<mat-tab label="NVMe">
|
||||||
|
|
||||||
<div class="flex flex-col mt-5 gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="gray-200 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>Critical Error Threshold</mat-label>
|
||||||
<input 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>Critical Warning Threshold</mat-label>
|
||||||
<input 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 class="flex flex-col mt-5 gt-md:flex-row">
|
<div matTooltip="not yet implemented" class="gray-200 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>Critical Error Threshold</mat-label>
|
||||||
<input 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>Critical Warning Threshold</mat-label>
|
||||||
<input matInput>
|
<input disabled matInput>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
</mat-tab-group>
|
</mat-tab-group>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
<mat-dialog-actions align="end">
|
<mat-dialog-actions align="end">
|
||||||
<button mat-button mat-dialog-close>Cancel</button>
|
<button mat-button mat-dialog-close>Cancel</button>
|
||||||
<button mat-button matTooltip="not yet implemented" [mat-dialog-close]="true" cdkFocusInitial>Save</button>
|
<button mat-button mat-dialog-close (click)="saveSettings()" cdkFocusInitial>Save</button>
|
||||||
</mat-dialog-actions>
|
</mat-dialog-actions>
|
||||||
|
|||||||
+36
-1
@@ -1,4 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import {AppConfig} from 'app/core/config/app.config';
|
||||||
|
import { TreoConfigService } from '@treo/services/config';
|
||||||
|
import {Subject} from "rxjs";
|
||||||
|
import {takeUntil} from "rxjs/operators";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard-settings',
|
selector: 'app-dashboard-settings',
|
||||||
@@ -7,10 +11,41 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class DashboardSettingsComponent implements OnInit {
|
export class DashboardSettingsComponent implements OnInit {
|
||||||
|
|
||||||
constructor() { }
|
dashboardDisplay: string;
|
||||||
|
dashboardSort: string;
|
||||||
|
|
||||||
|
// Private
|
||||||
|
private _unsubscribeAll: Subject<any>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _configService: TreoConfigService,
|
||||||
|
) {
|
||||||
|
// Set the private defaults
|
||||||
|
this._unsubscribeAll = new Subject();
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
// Subscribe to config changes
|
||||||
|
this._configService.config$
|
||||||
|
.pipe(takeUntil(this._unsubscribeAll))
|
||||||
|
.subscribe((config: AppConfig) => {
|
||||||
|
|
||||||
|
// Store the config
|
||||||
|
this.dashboardDisplay = config.dashboardDisplay;
|
||||||
|
this.dashboardSort = config.dashboardSort;
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveSettings(): void {
|
||||||
|
var newSettings = {
|
||||||
|
dashboardDisplay: this.dashboardDisplay,
|
||||||
|
dashboardSort: this.dashboardSort
|
||||||
|
}
|
||||||
|
this._configService.config = newSettings
|
||||||
|
console.log(`Saved Settings: ${JSON.stringify(newSettings)}`)
|
||||||
|
}
|
||||||
|
|
||||||
formatLabel(value: number) {
|
formatLabel(value: number) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,71 +47,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap w-full">
|
|
||||||
<div *ngFor="let summary of data.data.summary | keyvalue" class="flex gt-sm:w-1/2 min-w-80 p-4">
|
<div class="flex flex-wrap w-full" *ngFor="let hostId of hostGroups | keyvalue">
|
||||||
<div [ngClass]="{ 'border-green': summary.value.device.device_status == 0 && summary.value.smart,
|
<h3 class="ml-4" *ngIf="hostId.key">{{hostId.key}}</h3>
|
||||||
'border-red': summary.value.device.device_status != 0 }"
|
<div class="flex flex-wrap w-full">
|
||||||
class="relative flex flex-col flex-auto p-6 pr-3 pb-3 bg-card rounded border-l-4 shadow-md overflow-hidden">
|
<app-dashboard-device class="flex gt-sm:w-1/2 min-w-80 p-4" *ngFor="let deviceSummary of (deviceSummariesForHostGroup(hostId.value) | deviceSort:config.dashboardSort:config.dashboardDisplay )" [deviceWWN]="deviceSummary.device.wwn" [deviceSummary]="deviceSummary"></app-dashboard-device>
|
||||||
<div class="absolute bottom-0 right-0 w-24 h-24 -m-6">
|
|
||||||
<mat-icon class="icon-size-96 opacity-12 text-green"
|
|
||||||
*ngIf="summary.value.device.device_status == 0 && summary.value.smart"
|
|
||||||
[svgIcon]="'heroicons_outline:check-circle'"></mat-icon>
|
|
||||||
<mat-icon class="icon-size-96 opacity-12 text-red"
|
|
||||||
*ngIf="summary.value.device.device_status != 0"
|
|
||||||
[svgIcon]="'heroicons_outline:exclamation-circle'"></mat-icon>
|
|
||||||
<mat-icon class="icon-size-96 opacity-12 text-yellow"
|
|
||||||
*ngIf="!summary.value.smart"
|
|
||||||
[svgIcon]="'heroicons_outline:question-mark-circle'"></mat-icon>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<a [routerLink]="'/device/'+ summary.value.device.wwn"
|
|
||||||
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceTitle(summary.value.device)}}</a>
|
|
||||||
<div [ngClass]="classDeviceLastUpdatedOn(summary.value)" class="font-medium text-sm" *ngIf="summary.value.smart">
|
|
||||||
Last Updated on {{summary.value.smart.collector_date | date:'MMMM dd, yyyy - HH:mm' }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="ml-auto" *ngIf="summary.value.device">
|
|
||||||
<button mat-icon-button
|
|
||||||
[matMenuTriggerFor]="previousStatementMenu">
|
|
||||||
<mat-icon [svgIcon]="'more_vert'"></mat-icon>
|
|
||||||
</button>
|
|
||||||
<mat-menu #previousStatementMenu="matMenu">
|
|
||||||
<a mat-menu-item [routerLink]="'/device/'+ summary.value.device.wwn">
|
|
||||||
<span class="flex items-center">
|
|
||||||
<mat-icon class="icon-size-20 mr-3"
|
|
||||||
[svgIcon]="'payment'"></mat-icon>
|
|
||||||
<span>View Details</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</mat-menu>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row flex-wrap mt-4 -mx-6">
|
|
||||||
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
|
||||||
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Status</div>
|
|
||||||
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="summary.value.smart?.collector_date; else unknownStatus">{{ deviceStatusString(summary.value.device.device_status) | titlecase}}</div>
|
|
||||||
<ng-template #unknownStatus><div class="mt-2 font-medium text-3xl leading-none">No Data</div></ng-template>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
|
||||||
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Temperature</div>
|
|
||||||
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="summary.value.smart?.collector_date; else unknownTemp">{{ summary.value.smart?.temp }}°C</div>
|
|
||||||
<ng-template #unknownTemp><div class="mt-2 font-medium text-3xl leading-none">--</div></ng-template>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
|
||||||
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Capacity</div>
|
|
||||||
<div class="mt-2 font-medium text-3xl leading-none">{{ summary.value.device.capacity | fileSize}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col mx-6 my-3 xs:w-full">
|
|
||||||
<div class="font-semibold text-xs text-hint uppercase tracking-wider leading-none">Powered On</div>
|
|
||||||
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="summary.value.smart?.power_on_hours; else unknownPoweredOn">{{ humanizeDuration(summary.value.smart?.power_on_hours * 60 * 60 * 1000, { round: true, largest: 1, units: ['y', 'd', 'h'] }) }}</div>
|
|
||||||
<ng-template #unknownPoweredOn><div class="mt-2 font-medium text-3xl leading-none">--</div></ng-template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- Drive Temperatures -->
|
<!-- Drive Temperatures -->
|
||||||
<div class="flex flex-auto w-full min-w-80 h-90 p-4">
|
<div class="flex flex-auto w-full min-w-80 h-90 p-4">
|
||||||
<div class="flex flex-col flex-auto bg-card shadow-md rounded overflow-hidden">
|
<div class="flex flex-col flex-auto bg-card shadow-md rounded overflow-hidden">
|
||||||
@@ -123,22 +67,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button class="h-8 min-h-8 px-2"
|
<button class="h-8 min-h-8 px-2"
|
||||||
matTooltip="not yet implemented"
|
|
||||||
mat-button
|
mat-button
|
||||||
[matMenuTriggerFor]="tempRangeMenu">
|
[matMenuTriggerFor]="tempRangeMenu">
|
||||||
<span class="font-medium text-sm text-hint">1 week</span>
|
<span class="font-medium text-sm text-hint">{{tempDurationKey}}</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #tempRangeMenu="matMenu">
|
<mat-menu #tempRangeMenu="matMenu">
|
||||||
<button mat-menu-item>1 month</button>
|
<button (click)="changeSummaryTempDuration('forever')" mat-menu-item>forever</button>
|
||||||
<button mat-menu-item>12 months</button>
|
<button (click)="changeSummaryTempDuration('year')" mat-menu-item>year</button>
|
||||||
<button mat-menu-item>all time</button>
|
<button (click)="changeSummaryTempDuration('month')" mat-menu-item>month</button>
|
||||||
|
<button (click)="changeSummaryTempDuration('week')" mat-menu-item>week</button>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col flex-auto">
|
<div class="flex flex-col flex-auto">
|
||||||
<apx-chart *ngIf="temperatureOptions" class="flex-auto w-full h-full"
|
<apx-chart #tempChart *ngIf="temperatureOptions" class="flex-auto w-full h-full"
|
||||||
[chart]="temperatureOptions.chart"
|
[chart]="temperatureOptions.chart"
|
||||||
[colors]="temperatureOptions.colors"
|
[colors]="temperatureOptions.colors"
|
||||||
[fill]="temperatureOptions.fill"
|
[fill]="temperatureOptions.fill"
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { MatSort } from '@angular/material/sort';
|
|||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { ApexOptions } from 'ng-apexcharts';
|
import {ApexOptions, ChartComponent} from 'ng-apexcharts';
|
||||||
import { DashboardService } from 'app/modules/dashboard/dashboard.service';
|
import { DashboardService } from 'app/modules/dashboard/dashboard.service';
|
||||||
import * as moment from "moment";
|
|
||||||
import {MatDialog} from '@angular/material/dialog';
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
import { DashboardSettingsComponent } from 'app/layout/common/dashboard-settings/dashboard-settings.component';
|
import { DashboardSettingsComponent } from 'app/layout/common/dashboard-settings/dashboard-settings.component';
|
||||||
import humanizeDuration from 'humanize-duration'
|
import {deviceDisplayTitle} from "app/layout/common/dashboard-device/dashboard-device.component";
|
||||||
|
import {AppConfig} from "app/core/config/app.config";
|
||||||
|
import {TreoConfigService} from "@treo/services/config";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector : 'example',
|
selector : 'example',
|
||||||
@@ -20,10 +22,14 @@ import humanizeDuration from 'humanize-duration'
|
|||||||
export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
||||||
{
|
{
|
||||||
data: any;
|
data: any;
|
||||||
|
hostGroups: { [hostId: string]: string[] } = {}
|
||||||
temperatureOptions: ApexOptions;
|
temperatureOptions: ApexOptions;
|
||||||
|
tempDurationKey: string = "forever"
|
||||||
|
config: AppConfig;
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
private _unsubscribeAll: Subject<any>;
|
private _unsubscribeAll: Subject<any>;
|
||||||
|
@ViewChild("tempChart", { static: false }) tempChart: ChartComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
@@ -32,7 +38,9 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
constructor(
|
constructor(
|
||||||
private _smartService: DashboardService,
|
private _smartService: DashboardService,
|
||||||
public dialog: MatDialog
|
private _configService: TreoConfigService,
|
||||||
|
public dialog: MatDialog,
|
||||||
|
private router: Router,
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
// Set the private defaults
|
// Set the private defaults
|
||||||
@@ -49,6 +57,28 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void
|
ngOnInit(): void
|
||||||
{
|
{
|
||||||
|
|
||||||
|
// Subscribe to config changes
|
||||||
|
this._configService.config$
|
||||||
|
.pipe(takeUntil(this._unsubscribeAll))
|
||||||
|
.subscribe((config: AppConfig) => {
|
||||||
|
|
||||||
|
//check if the old config and the new config do not match.
|
||||||
|
let oldConfig = JSON.stringify(this.config)
|
||||||
|
let newConfig = JSON.stringify(config)
|
||||||
|
|
||||||
|
if(oldConfig != newConfig){
|
||||||
|
console.log(`Configuration updated: ${newConfig} vs ${oldConfig}`)
|
||||||
|
// Store the config
|
||||||
|
this.config = config;
|
||||||
|
|
||||||
|
if(oldConfig){
|
||||||
|
console.log("reloading component...")
|
||||||
|
this.refreshComponent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Get the data
|
// Get the data
|
||||||
this._smartService.data$
|
this._smartService.data$
|
||||||
.pipe(takeUntil(this._unsubscribeAll))
|
.pipe(takeUntil(this._unsubscribeAll))
|
||||||
@@ -57,6 +87,15 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
// Store the data
|
// Store the data
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
|
||||||
|
//generate group data.
|
||||||
|
for(let wwn in this.data.data.summary){
|
||||||
|
let hostid = this.data.data.summary[wwn].device.host_id
|
||||||
|
let hostDeviceList = this.hostGroups[hostid] || []
|
||||||
|
hostDeviceList.push(wwn)
|
||||||
|
this.hostGroups[hostid] = hostDeviceList
|
||||||
|
}
|
||||||
|
console.log(this.hostGroups)
|
||||||
|
|
||||||
// Prepare the chart data
|
// Prepare the chart data
|
||||||
this._prepareChartData();
|
this._prepareChartData();
|
||||||
});
|
});
|
||||||
@@ -81,6 +120,14 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
// @ Private methods
|
// @ Private methods
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
private refreshComponent(){
|
||||||
|
|
||||||
|
let currentUrl = this.router.url;
|
||||||
|
this.router.routeReuseStrategy.shouldReuseRoute = () => false;
|
||||||
|
this.router.onSameUrlNavigation = 'reload';
|
||||||
|
this.router.navigate([currentUrl]);
|
||||||
|
}
|
||||||
|
|
||||||
private _deviceDataTemperatureSeries() {
|
private _deviceDataTemperatureSeries() {
|
||||||
var deviceTemperatureSeries = []
|
var deviceTemperatureSeries = []
|
||||||
|
|
||||||
@@ -91,8 +138,11 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
if (!deviceSummary.temp_history){
|
if (!deviceSummary.temp_history){
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let deviceName = this.deviceTitle(deviceSummary.device)
|
||||||
|
|
||||||
var deviceSeriesMetadata = {
|
var deviceSeriesMetadata = {
|
||||||
name: `/dev/${deviceSummary.device.device_name}`,
|
name: deviceName,
|
||||||
data: []
|
data: []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +214,26 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
// @ Public methods
|
// @ Public methods
|
||||||
// -----------------------------------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
deviceTitle(disk){
|
||||||
|
|
||||||
|
console.log(`Displaying Device ${disk.wwn} with: ${this.config.dashboardDisplay}`)
|
||||||
|
let titleParts = []
|
||||||
|
if (disk.host_id) titleParts.push(disk.host_id)
|
||||||
|
|
||||||
|
//add device identifier (fallback to generated device name)
|
||||||
|
titleParts.push(deviceDisplayTitle(disk, this.config.dashboardDisplay) || deviceDisplayTitle(disk, 'name'))
|
||||||
|
|
||||||
|
return titleParts.join(' - ')
|
||||||
|
}
|
||||||
|
|
||||||
|
deviceSummariesForHostGroup(hostGroupWWNs: string[]) {
|
||||||
|
let deviceSummaries = []
|
||||||
|
for(let wwn of hostGroupWWNs){
|
||||||
|
deviceSummaries.push(this.data.data.summary[wwn])
|
||||||
|
}
|
||||||
|
return deviceSummaries
|
||||||
|
}
|
||||||
|
|
||||||
openDialog() {
|
openDialog() {
|
||||||
const dialogRef = this.dialog.open(DashboardSettingsComponent);
|
const dialogRef = this.dialog.open(DashboardSettingsComponent);
|
||||||
|
|
||||||
@@ -172,48 +242,29 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceTitle(disk){
|
/*
|
||||||
let title = []
|
|
||||||
|
|
||||||
if (disk.host_id) title.push(disk.host_id)
|
DURATION_KEY_WEEK = "week"
|
||||||
|
DURATION_KEY_MONTH = "month"
|
||||||
|
DURATION_KEY_YEAR = "year"
|
||||||
|
DURATION_KEY_FOREVER = "forever"
|
||||||
|
*/
|
||||||
|
|
||||||
title.push(`/dev/${disk.device_name}`)
|
changeSummaryTempDuration(durationKey: string){
|
||||||
|
this.tempDurationKey = durationKey
|
||||||
|
|
||||||
if (disk.device_type && disk.device_type != 'scsi' && disk.device_type != 'ata'){
|
this._smartService.getSummaryTempData(durationKey)
|
||||||
title.push(disk.device_type)
|
.subscribe((data) => {
|
||||||
}
|
|
||||||
|
|
||||||
title.push(disk.model_name)
|
// given a list of device temp history, override the data in the "summary" object.
|
||||||
|
for(const wwn in this.data.data.summary) {
|
||||||
|
// console.log(`Updating ${wwn}, length: ${this.data.data.summary[wwn].temp_history.length}`)
|
||||||
|
this.data.data.summary[wwn].temp_history = data.data.temp_history[wwn] || []
|
||||||
|
}
|
||||||
|
|
||||||
return title.join(' - ')
|
// Prepare the chart series data
|
||||||
}
|
this.tempChart.updateSeries(this._deviceDataTemperatureSeries())
|
||||||
|
});
|
||||||
deviceStatusString(deviceStatus){
|
|
||||||
if(deviceStatus == 0){
|
|
||||||
return "passed"
|
|
||||||
} else {
|
|
||||||
return "failed"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
classDeviceLastUpdatedOn(deviceSummary){
|
|
||||||
if (deviceSummary.device.device_status !== 0) {
|
|
||||||
return 'text-red' // if the device has failed, always highlight in red
|
|
||||||
} else if(deviceSummary.device.device_status === 0 && deviceSummary.smart){
|
|
||||||
if(moment().subtract(14, 'd').isBefore(deviceSummary.smart.collector_date)){
|
|
||||||
// this device was updated in the last 2 weeks.
|
|
||||||
return 'text-green'
|
|
||||||
} else if(moment().subtract(1, 'm').isBefore(deviceSummary.smart.collector_date)){
|
|
||||||
// this device was updated in the last month
|
|
||||||
return 'text-yellow'
|
|
||||||
} else{
|
|
||||||
// last updated more than a month ago.
|
|
||||||
return 'text-red'
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,6 +278,4 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
|
|||||||
return item.id || index;
|
return item.id || index;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly humanizeDuration = humanizeDuration;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import { MatTableModule } from '@angular/material/table';
|
|||||||
import { NgApexchartsModule } from 'ng-apexcharts';
|
import { NgApexchartsModule } from 'ng-apexcharts';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip'
|
import { MatTooltipModule } from '@angular/material/tooltip'
|
||||||
import { DashboardSettingsModule } from "app/layout/common/dashboard-settings/dashboard-settings.module";
|
import { DashboardSettingsModule } from "app/layout/common/dashboard-settings/dashboard-settings.module";
|
||||||
|
import { DashboardDeviceModule } from "app/layout/common/dashboard-device/dashboard-device.module";
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
DashboardComponent
|
DashboardComponent
|
||||||
],
|
],
|
||||||
imports : [
|
imports: [
|
||||||
RouterModule.forChild(dashboardRoutes),
|
RouterModule.forChild(dashboardRoutes),
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatDividerModule,
|
MatDividerModule,
|
||||||
@@ -30,7 +31,8 @@ import { DashboardSettingsModule } from "app/layout/common/dashboard-settings/da
|
|||||||
MatTableModule,
|
MatTableModule,
|
||||||
NgApexchartsModule,
|
NgApexchartsModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
DashboardSettingsModule
|
DashboardSettingsModule,
|
||||||
|
DashboardDeviceModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class DashboardModule
|
export class DashboardModule
|
||||||
|
|||||||
@@ -31,6 +31,6 @@ export class DashboardResolver implements Resolve<any>
|
|||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
|
||||||
{
|
{
|
||||||
return this._dashboardService.getData();
|
return this._dashboardService.getSummaryData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export class DashboardService
|
|||||||
/**
|
/**
|
||||||
* Get data
|
* Get data
|
||||||
*/
|
*/
|
||||||
getData(): Observable<any>
|
getSummaryData(): Observable<any>
|
||||||
{
|
{
|
||||||
return this._httpClient.get(getBasePath() + '/api/summary').pipe(
|
return this._httpClient.get(getBasePath() + '/api/summary').pipe(
|
||||||
tap((response: any) => {
|
tap((response: any) => {
|
||||||
@@ -52,4 +52,14 @@ export class DashboardService
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSummaryTempData(durationKey: string): Observable<any>
|
||||||
|
{
|
||||||
|
let params = {}
|
||||||
|
if(durationKey){
|
||||||
|
params["duration_key"] = durationKey
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._httpClient.get(getBasePath() + '/api/summary/temp', {params: params});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +1,60 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core';
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import {deviceDisplayTitle} from "app/layout/common/dashboard-device/dashboard-device.component";
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'deviceSort'
|
name: 'deviceSort'
|
||||||
})
|
})
|
||||||
export class DeviceSortPipe implements PipeTransform {
|
export class DeviceSortPipe implements PipeTransform {
|
||||||
|
|
||||||
numericalStatus(device): number {
|
statusCompareFn(a: any, b: any) {
|
||||||
if(!device.smart_results[0]){
|
function deviceStatus(deviceSummary): number {
|
||||||
return 0
|
if(!deviceSummary.smart){
|
||||||
} else if (device.smart_results[0].smart_status == 'passed'){
|
return 0
|
||||||
return 1
|
} else if (deviceSummary.device.device_status == 0){
|
||||||
} else {
|
return 1
|
||||||
return -1
|
} else {
|
||||||
|
return deviceSummary.device.device_status * -1 // will return range from -1, -2, -3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let left = deviceStatus(a)
|
||||||
|
let right = deviceStatus(b)
|
||||||
|
|
||||||
|
return left - right;
|
||||||
|
}
|
||||||
|
|
||||||
|
titleCompareFn(dashboardDisplay: string) {
|
||||||
|
return function (a: any, b: any){
|
||||||
|
let _dashboardDisplay = dashboardDisplay
|
||||||
|
let left = deviceDisplayTitle(a.device, _dashboardDisplay) || deviceDisplayTitle(a.device, 'name')
|
||||||
|
let right = deviceDisplayTitle(b.device, _dashboardDisplay) || deviceDisplayTitle(b.device, 'name')
|
||||||
|
|
||||||
|
if( left < right )
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if( left > right )
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
transform(devices: Array<unknown>, ...args: unknown[]): Array<unknown> {
|
transform(deviceSummaries: Array<unknown>, sortBy = 'status', dashboardDisplay = "name"): Array<unknown> {
|
||||||
|
let compareFn = undefined
|
||||||
|
switch (sortBy) {
|
||||||
|
case 'status':
|
||||||
|
compareFn = this.statusCompareFn
|
||||||
|
break;
|
||||||
|
case 'title':
|
||||||
|
compareFn = this.titleCompareFn(dashboardDisplay)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
//failed, unknown/empty, passed
|
//failed, unknown/empty, passed
|
||||||
devices.sort((a: any, b: any) => {
|
deviceSummaries.sort(compareFn);
|
||||||
|
|
||||||
let left = this.numericalStatus(a)
|
return deviceSummaries;
|
||||||
let right = this.numericalStatus(b)
|
|
||||||
|
|
||||||
return left - right;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
return devices;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user