added debug logging message for detected devices.
adding a mocked class for Config. Adding device type to Device struct. Will eventually be needed for raid drives. adding End-to-end testing capabilties. Added testdata json files for webserver requests. Seperated Start code and Setup code in webapp so we can test. renamed "smart_attributes" to "ata_attributes" - Backwards incomatible change. Added front end device sorting (red, yellow, green) show unknown icon/status if drive has no smart data yet. Moved all attribute "getters" into the controller. created a device-sort pipe.
This commit is contained in:
@@ -31,7 +31,7 @@ export const details = {
|
||||
"temp": 35,
|
||||
"power_on_hours": 48706,
|
||||
"power_cycle_count": 69,
|
||||
"smart_attributes": [
|
||||
"ata_attributes": [
|
||||
{
|
||||
"ID": 2632,
|
||||
"CreatedAt": "2020-08-14T03:41:00.763627397Z",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -51,8 +51,9 @@
|
||||
|
||||
<div class="flex flex-wrap w-full">
|
||||
|
||||
<div *ngFor="let disk of data.data " class="flex w-1/2 min-w-80 p-4">
|
||||
<div [ngClass]="{'border-green': disk.smart_results[0]?.smart_status == 'passed', 'border-red': disk.smart_results[0]?.smart_status == 'failed'}"
|
||||
<div *ngFor="let disk of data.data | deviceSort" class="flex w-1/2 min-w-80 p-4">
|
||||
<div [ngClass]="{'border-green': disk.smart_results[0]?.smart_status == 'passed',
|
||||
'border-red': disk.smart_results[0]?.smart_status == 'failed' }"
|
||||
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"
|
||||
@@ -61,12 +62,16 @@
|
||||
<mat-icon class="icon-size-96 opacity-12 text-red"
|
||||
*ngIf="disk.smart_results[0]?.smart_status == 'failed'"
|
||||
[svgIcon]="'heroicons_outline:exclamation-circle'"></mat-icon>
|
||||
<mat-icon class="icon-size-96 opacity-12 text-yellow"
|
||||
*ngIf="!disk.smart_results[0]"
|
||||
[svgIcon]="'heroicons_outline:question-mark-circle'"></mat-icon>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<div class="flex flex-col">
|
||||
<a [routerLink]="'/device/'+ disk.wwn"
|
||||
class="font-bold text-md text-secondary uppercase tracking-wider">/dev/{{disk.device_name}} - {{disk.model_name}}</a>
|
||||
<div class="text-green font-medium text-sm">
|
||||
<div [ngClass]="{'text-green': disk.smart_results[0]?.smart_status == 'passed',
|
||||
'text-red': disk.smart_results[0]?.smart_status == 'failed' }" class="font-medium text-sm" *ngIf="disk.smart_results[0]">
|
||||
Last Updated on {{disk.smart_results[0]?.date | date:'MMMM dd, yyyy' }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,11 +94,13 @@
|
||||
<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">S.M.A.R.T</div>
|
||||
<div class="mt-2 font-medium text-3xl leading-none">{{ disk.smart_results[0]?.smart_status | titlecase}}</div>
|
||||
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="disk.smart_results[0]; else unknownStatus">{{ disk.smart_results[0]?.smart_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">{{ disk.smart_results[0]?.temp }}°C</div>
|
||||
<div class="mt-2 font-medium text-3xl leading-none" *ngIf="disk.smart_results[0]; else unknownTemp">{{ disk.smart_results[0]?.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>
|
||||
|
||||
@@ -83,6 +83,10 @@
|
||||
<div>{{data.data.rotational_speed}} RPM</div>
|
||||
<div class="text-secondary text-md">Rotation Rate</div>
|
||||
</div>
|
||||
<div *ngIf="data.data.device_protocol" class="my-2">
|
||||
<div>{{data.data.device_protocol}}</div>
|
||||
<div class="text-secondary text-md">Protocol</div>
|
||||
</div>
|
||||
<div class="my-2">
|
||||
<div>{{data.data.smart_results[0]?.power_cycle_count}}</div>
|
||||
<div class="text-secondary text-md">Power Cycle Count</div>
|
||||
@@ -103,7 +107,7 @@
|
||||
<div class="flex flex-col flex-auto bg-card shadow-md rounded ">
|
||||
<div class="p-6">
|
||||
<div class="font-bold text-md text-secondary uppercase tracking-wider">S.M.A.R.T Attributes</div>
|
||||
<div class="text-sm text-hint font-medium">{{this.smartAttributeDataSource.data.length}} visible, {{this.data.data.smart_results[0]?.smart_attributes.length - this.smartAttributeDataSource.data.length}} hidden</div>
|
||||
<div class="text-sm text-hint font-medium">{{this.smartAttributeDataSource.data.length}} visible, {{this.data.data.smart_results[0]?.ata_attributes.length - this.smartAttributeDataSource.data.length}} hidden</div>
|
||||
</div>
|
||||
<div class="overflow-auto">
|
||||
<table class="w-full bg-transparent"
|
||||
@@ -169,7 +173,7 @@
|
||||
</th>
|
||||
<td mat-cell
|
||||
*matCellDef="let attribute">
|
||||
<span class="pr-6 whitespace-no-wrap" matTooltip="{{data.lookup[attribute.attribute_id]?.description}}">
|
||||
<span class="pr-6 whitespace-no-wrap" matTooltip="{{getAttributeDescription(attribute)}}">
|
||||
{{attribute.name}} <mat-icon class="icon-size-10" [svgIcon]="'info'"></mat-icon>
|
||||
</span>
|
||||
</td>
|
||||
@@ -187,8 +191,8 @@
|
||||
</th>
|
||||
<td mat-cell
|
||||
*matCellDef="let attribute">
|
||||
<span class="pr-6 whitespace-no-wrap" matTooltip="{{data.lookup[attribute.attribute_id].display_type}}">
|
||||
{{extractAttributeValue(data.lookup[attribute.attribute_id], attribute)}}
|
||||
<span class="pr-6 whitespace-no-wrap" matTooltip="{{getAttributeValueType(attribute)}}">
|
||||
{{getAttributeValue(attribute)}}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -206,7 +210,7 @@
|
||||
<td mat-cell
|
||||
*matCellDef="let attribute">
|
||||
<span class="pr-6 whitespace-no-wrap">
|
||||
{{attribute.worst}}
|
||||
{{getAttributeWorst(attribute)}}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -224,7 +228,7 @@
|
||||
<td mat-cell
|
||||
*matCellDef="let attribute">
|
||||
<span class="pr-6 whitespace-no-wrap">
|
||||
{{attribute.thresh}}
|
||||
{{getAttributeThreshold(attribute)}}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
@@ -242,7 +246,7 @@
|
||||
<td mat-cell
|
||||
*matCellDef="let attribute">
|
||||
<span class="pr-6 font-medium whitespace-no-wrap">
|
||||
{{data.lookup[attribute.attribute_id]?.display_type == "raw" ? data.lookup[attribute.attribute_id]?.ideal : '' }}
|
||||
{{getAttributeIdeal(attribute) }}
|
||||
</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -93,8 +93,12 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
// @ Private methods
|
||||
// -----------------------------------------------------------------------------------------------------
|
||||
getAttributeDescription(attribute_data){
|
||||
return this.data.lookup[attribute_data.attribute_id]?.description
|
||||
}
|
||||
|
||||
extractAttributeValue(attribute_metadata, attribute_data){
|
||||
getAttributeValue(attribute_data){
|
||||
let attribute_metadata = this.data.lookup[attribute_data.attribute_id]
|
||||
if(attribute_metadata.display_type == "raw"){
|
||||
return attribute_data.raw_value
|
||||
}
|
||||
@@ -105,6 +109,22 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
getAttributeValueType(attribute_data){
|
||||
let attribute_metadata = this.data.lookup[attribute_data.attribute_id]
|
||||
return this.data.lookup[attribute_data.attribute_id].display_type
|
||||
}
|
||||
|
||||
getAttributeIdeal(attribute_data){
|
||||
return this.data.lookup[attribute_data.attribute_id]?.display_type == "raw" ? this.data.lookup[attribute_data.attribute_id]?.ideal : ''
|
||||
}
|
||||
|
||||
getAttributeWorst(attribute_data){
|
||||
return attribute_data.worst
|
||||
}
|
||||
getAttributeThreshold(attribute_data){
|
||||
return attribute_data.thresh
|
||||
}
|
||||
|
||||
private _generateSmartAttributeTableDataSource(smart_results){
|
||||
var smartAttributeDataSource = [];
|
||||
|
||||
@@ -113,14 +133,11 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
}
|
||||
|
||||
var latest_smart_result = smart_results[0];
|
||||
for(let attr of latest_smart_result.smart_attributes){
|
||||
|
||||
|
||||
|
||||
for(let attr of latest_smart_result.ata_attributes){
|
||||
//chart history data
|
||||
if (!attr.chartData) {
|
||||
var rawHistory = (attr.history || []).map(hist_attr => this.extractAttributeValue(this.data.lookup[attr.attribute_id], hist_attr)).reverse()
|
||||
rawHistory.push(this.extractAttributeValue(this.data.lookup[attr.attribute_id], attr))
|
||||
var rawHistory = (attr.history || []).map(hist_attr => this.getAttributeValue(hist_attr)).reverse()
|
||||
rawHistory.push(this.getAttributeValue(attr))
|
||||
attr.chartData = [
|
||||
{
|
||||
name: "chart-line-sparkline",
|
||||
@@ -128,23 +145,6 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
data: rawHistory
|
||||
}
|
||||
]
|
||||
|
||||
// //add the reference line showing the threshold
|
||||
// attr.chartDataReferenceLine = {
|
||||
// yaxis: [
|
||||
// {
|
||||
// y: attr.thresh,
|
||||
// borderColor: '#f05252',
|
||||
// label: {
|
||||
// borderColor: '#f05252',
|
||||
// style: {
|
||||
// color: '#fff',
|
||||
// background: '#f05252'
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
}
|
||||
//determine when to include the attributes in table.
|
||||
if(!this.onlyCritical || this.onlyCritical && this.data.lookup[attr.attribute_id]?.critical || attr.value <= attr.thresh){
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { DeviceSortPipe } from './device-sort.pipe';
|
||||
|
||||
describe('DeviceSortPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new DeviceSortPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'deviceSort'
|
||||
})
|
||||
export class DeviceSortPipe implements PipeTransform {
|
||||
|
||||
numericalStatus(device): number {
|
||||
if(!device.smart_results[0]){
|
||||
return 0
|
||||
} else if (device.smart_results[0].smart_status == 'passed'){
|
||||
return 1
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
transform(devices: Array<unknown>, ...args: unknown[]): Array<unknown> {
|
||||
//failed, unknown/empty, passed
|
||||
devices.sort((a: any, b: any) => {
|
||||
|
||||
let left = this.numericalStatus(a)
|
||||
let right = this.numericalStatus(b)
|
||||
|
||||
return left - right;
|
||||
});
|
||||
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,10 +2,12 @@ import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import {FileSizePipe} from "./file-size.pipe";
|
||||
import { DeviceSortPipe } from './device-sort.pipe';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FileSizePipe
|
||||
FileSizePipe,
|
||||
DeviceSortPipe
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@@ -16,7 +18,8 @@ import {FileSizePipe} from "./file-size.pipe";
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
FileSizePipe
|
||||
FileSizePipe,
|
||||
DeviceSortPipe
|
||||
]
|
||||
})
|
||||
export class SharedModule
|
||||
|
||||
Reference in New Issue
Block a user