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:
Jason Kulatunga
2020-08-26 21:35:13 -07:00
parent 5a80ae3e74
commit 2ad120c87b
20 changed files with 1583 additions and 201 deletions
@@ -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