!!!!WIP!!!!

adding InfluxDB

- influxdb added to dockerfile
- influxdb s6 service
- influxdb config
- adding defaults to config
- creating a DeviceRepo interface (multiple db backends)
- implemented DeviceRepo interface as ScruitnyRepository
This commit is contained in:
Jason Kulatunga
2021-06-27 10:55:18 -07:00
parent fd4f0429e4
commit 8a46931399
53 changed files with 4343 additions and 10759 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,5 @@
<div *ngIf="data && data.data && data.data.length > 0; else emptyDashboard">
<div *ngIf="data && data.data && data.data.summary; else emptyDashboard">
<div class="flex flex-col flex-auto w-full p-8 xs:p-2">
<div class="flex flex-wrap w-full">
@@ -47,38 +48,37 @@
</div>
<div class="flex flex-wrap w-full">
<div *ngFor="let disk of data.data | deviceSort" class="flex gt-sm: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 summary of data.data.summary | keyvalue" class="flex gt-sm:w-1/2 min-w-80 p-4">
<div [ngClass]="{ 'border-green': summary.value.device.device_status == 0 && summary.value.smart,
'border-red': summary.value.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="disk.smart_results[0]?.smart_status == 'passed'"
*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="disk.smart_results[0]?.smart_status == 'failed'"
*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="!disk.smart_results[0]"
*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/'+ disk.wwn"
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceTitle(disk)}}</a>
<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 - HH:mm' }}
<a [routerLink]="'/device/'+ summary.value.device.wwn"
class="font-bold text-md text-secondary uppercase tracking-wider">{{deviceTitle(summary.value.device)}}</a>
<div [ngClass]="{'text-green': summary.value.device.device_status == 0 && summary.value.smart,
'text-red': summary.value.device.device_status != 0 }" 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="disk.smart_results">
<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/'+ disk.wwn">
<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>
@@ -90,22 +90,22 @@
</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">S.M.A.R.T</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>
<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="disk.smart_results[0]; else unknownTemp">{{ disk.smart_results[0]?.temp }}°C</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">{{ disk.capacity | fileSize}}</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="disk.smart_results[0]?.power_on_hours; else unknownPoweredOn">{{ humanizeDuration(disk.smart_results[0]?.power_on_hours * 60 * 60 * 1000, { round: true, largest: 1, units: ['y', 'd', 'h'] }) }}</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>
@@ -140,7 +140,7 @@
</div>
<div class="flex flex-col flex-auto">
<apx-chart class="flex-auto w-full h-full"
<apx-chart *ngIf="temperatureOptions" class="flex-auto w-full h-full"
[chart]="temperatureOptions.chart"
[colors]="temperatureOptions.colors"
[fill]="temperatureOptions.fill"
@@ -84,16 +84,23 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
private _deviceDataTemperatureSeries() {
var deviceTemperatureSeries = []
for(let device of this.data.data){
console.log("DEVICE DATA SUMMARY", this.data)
for(const wwn in this.data.data.summary){
var deviceSummary = this.data.data.summary[wwn]
if (!deviceSummary.temp_history){
continue
}
var deviceSeriesMetadata = {
name: `/dev/${device.device_name}`,
name: `/dev/${deviceSummary.device.device_name}`,
data: []
}
for(let smartResults of device.smart_results){
let newDate = new Date(smartResults.CreatedAt);
for(let tempHistory of deviceSummary.temp_history){
let newDate = new Date(tempHistory.date);
deviceSeriesMetadata.data.push({
x: newDate,
y: smartResults.temp
y: tempHistory.temp
})
}
deviceTemperatureSeries.push(deviceSeriesMetadata)
@@ -181,6 +188,14 @@ export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
return title.join(' - ')
}
deviceStatusString(deviceStatus){
if(deviceStatus == 0){
return "passed"
} else {
return "failed"
}
}
/**
* Track by function for ngFor loops
*
@@ -53,59 +53,59 @@
<div class="flex flex-auto w-1/4 p-4 lt-md:w-full">
<treo-card class="flex flex-auto p-4 pt-6 flex-col flex-auto filter-list">
<div class="flex items-center justify-between">
<div class="text-2xl font-semibold leading-tight">/dev/{{data.data.device_name}}</div>
<div class="text-2xl font-semibold leading-tight">/dev/{{device?.device_name}}</div>
</div>
<div class="flex flex-col my-2 grid grid-cols-2">
<div *ngIf="data.data.host_id" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.host_id}}</div>
<div *ngIf="device?.host_id" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{device?.host_id}}</div>
<div class="text-secondary text-md">Host ID</div>
</div>
<div *ngIf="data.data.device_type && data.data.device_type != 'ata' && data.data.device_type != 'scsi'" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.device_type | uppercase}}</div>
<div *ngIf="device?.device_type && device?.device_type != 'ata' && device?.device_type != 'scsi'" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{device?.device_type | uppercase}}</div>
<div class="text-secondary text-md">Device Type</div>
</div>
<div *ngIf="data.data.manufacturer" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.manufacturer}}</div>
<div *ngIf="device?.manufacturer" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{device?.manufacturer}}</div>
<div class="text-secondary text-md">Model Family</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.model_name}}</div>
<div>{{device?.model_name}}</div>
<div class="text-secondary text-md">Device Model</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.serial_number}}</div>
<div>{{device?.serial_number}}</div>
<div class="text-secondary text-md">Serial Number</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.wwn}}</div>
<div>{{device?.wwn}}</div>
<div class="text-secondary text-md">LU WWN Device Id</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.firmware}}</div>
<div>{{device?.firmware}}</div>
<div class="text-secondary text-md">Firmware Version</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.capacity | fileSize}}</div>
<div>{{device?.capacity | fileSize}}</div>
<div class="text-secondary text-md">Capacity</div>
</div>
<div *ngIf="data.data.rotational_speed" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.rotational_speed}} RPM</div>
<div *ngIf="device?.rotational_speed" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{device?.rotational_speed}} RPM</div>
<div class="text-secondary text-md">Rotation Rate</div>
</div>
<div *ngIf="data.data.device_protocol" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.device_protocol}}</div>
<div *ngIf="device?.device_protocol" class="my-2 col-span-2 lt-md:col-span-1">
<div>{{device?.device_protocol}}</div>
<div class="text-secondary text-md">Protocol</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.smart_results[0]?.power_cycle_count}}</div>
<div>{{smart_results[0]?.power_cycle_count}}</div>
<div class="text-secondary text-md">Power Cycle Count</div>
</div>
<div *ngIf="data.data.smart_results[0]?.power_on_hours" class="my-2 col-span-2 lt-md:col-span-1">
<div matTooltip="{{humanizeDuration(data.data.smart_results[0]?.power_on_hours * 60 * 60 * 1000, { conjunction: ' and ', serialComma: false })}}">{{humanizeDuration(data.data.smart_results[0]?.power_on_hours *60 * 60 * 1000, { round: true, largest: 1, units: ['y', 'd', 'h'] })}}</div>
<div *ngIf="smart_results[0]?.power_on_hours" class="my-2 col-span-2 lt-md:col-span-1">
<div matTooltip="{{humanizeDuration(smart_results[0]?.power_on_hours * 60 * 60 * 1000, { conjunction: ' and ', serialComma: false })}}">{{humanizeDuration(smart_results[0]?.power_on_hours *60 * 60 * 1000, { round: true, largest: 1, units: ['y', 'd', 'h'] })}}</div>
<div class="text-secondary text-md">Powered On</div>
</div>
<div class="my-2 col-span-2 lt-md:col-span-1">
<div>{{data.data.smart_results[0]?.temp}}°C</div>
<div>{{smart_results[0]?.temp}}°C</div>
<div class="text-secondary text-md">Temperature</div>
</div>
</div>
@@ -115,7 +115,7 @@
<div class="flex flex-auto w-3/4 p-4 lt-md:w-full">
<div class="flex flex-col flex-auto w-full 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 {{data.data.device_protocol}} Attributes</div>
<div class="font-bold text-md text-secondary uppercase tracking-wider">S.M.A.R.T {{device?.device_protocol}} Attributes</div>
<div class="text-sm text-hint font-medium">{{this.smartAttributeDataSource.data.length}} visible, {{getHiddenAttributes()}} hidden</div>
</div>
<div class="overflow-auto">
@@ -19,7 +19,12 @@ import humanizeDuration from 'humanize-duration';
export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
onlyCritical: boolean = true;
data: any;
// data: any;
metadata: any;
device: any;
smart_results: any[];
commonSparklineOptions: Partial<ApexOptions>;
smartAttributeDataSource: MatTableDataSource<any>;
smartAttributeTableColumns: string[];
@@ -66,10 +71,14 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
.subscribe((data) => {
// Store the data
this.data = data;
// this.data = data;
this.device = data.data.device;
this.smart_results = data.data.smart_results
this.metadata = data.metadata;
// Store the table data
this.smartAttributeDataSource.data = this._generateSmartAttributeTableDataSource(data.data.smart_results);
this.smartAttributeDataSource.data = this._generateSmartAttributeTableDataSource(this.smart_results);
// Prepare the chart data
this._prepareChartData();
@@ -99,7 +108,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
// @ Private methods
// -----------------------------------------------------------------------------------------------------
getAttributeDescription(attribute_data){
let attribute_metadata = this.data.metadata[attribute_data.attribute_id]
let attribute_metadata = this.metadata[attribute_data.attribute_id]
if(!attribute_metadata){
return 'Unknown'
} else {
@@ -110,7 +119,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
getAttributeValue(attribute_data){
if(this.isAta()) {
let attribute_metadata = this.data.metadata[attribute_data.attribute_id]
let attribute_metadata = this.metadata[attribute_data.attribute_id]
if(!attribute_metadata){
return attribute_data.value
} else if (attribute_metadata.display_type == "raw") {
@@ -128,7 +137,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
getAttributeValueType(attribute_data){
if(this.isAta()) {
let attribute_metadata = this.data.metadata[attribute_data.attribute_id]
let attribute_metadata = this.metadata[attribute_data.attribute_id]
if(!attribute_metadata){
return ''
} else {
@@ -141,14 +150,14 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
getAttributeIdeal(attribute_data){
if(this.isAta()){
return this.data.metadata[attribute_data.attribute_id]?.display_type == "raw" ? this.data.metadata[attribute_data.attribute_id]?.ideal : ''
return this.metadata[attribute_data.attribute_id]?.display_type == "raw" ? this.metadata[attribute_data.attribute_id]?.ideal : ''
} else {
return this.data.metadata[attribute_data.attribute_id]?.ideal
return this.metadata[attribute_data.attribute_id]?.ideal
}
}
getAttributeWorst(attribute_data){
let attribute_metadata = this.data.metadata[attribute_data.attribute_id]
let attribute_metadata = this.metadata[attribute_data.attribute_id]
if(!attribute_metadata){
return attribute_data.worst
} else {
@@ -158,7 +167,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
getAttributeThreshold(attribute_data){
if(this.isAta()){
let attribute_metadata = this.data.metadata[attribute_data.attribute_id]
let attribute_metadata = this.metadata[attribute_data.attribute_id]
if(!attribute_metadata || attribute_metadata.display_type == "normalized"){
return attribute_data.thresh
} else {
@@ -175,29 +184,30 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
}
getAttributeCritical(attribute_data){
return this.data.metadata[attribute_data.attribute_id]?.critical
return this.metadata[attribute_data.attribute_id]?.critical
}
getHiddenAttributes(){
let attributes_list
if(this.isAta()){
attributes_list = this.data.data.smart_results[0]?.ata_attributes
} else if(this.isNvme()){
attributes_list = this.data.data.smart_results[0]?.nvme_attributes
} else {
attributes_list = this.data.data.smart_results[0]?.scsi_attributes
if (!this.smart_results || this.smart_results.length == 0) {
return 0
}
return attributes_list.length - this.smartAttributeDataSource.data.length
let attributes_length = 0
let attributes = this.smart_results[0]?.attrs
if (attributes) {
attributes_length = Object.keys(attributes).length
}
return attributes_length - this.smartAttributeDataSource.data.length
}
isAta(): boolean {
return this.data.data.device_protocol == 'ATA'
return this.device.device_protocol == 'ATA'
}
isScsi(): boolean {
return this.data.data.device_protocol == 'SCSI'
return this.device.device_protocol == 'SCSI'
}
isNvme(): boolean {
return this.data.data.device_protocol == 'NVMe'
return this.device.device_protocol == 'NVMe'
}
private _generateSmartAttributeTableDataSource(smart_results){
@@ -207,21 +217,22 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
return smartAttributeDataSource
}
var latest_smart_result = smart_results[0];
let attributes_list = []
let attributes = {}
if(this.isScsi()) {
this.smartAttributeTableColumns = ['status', 'name', 'value', 'thresh', 'history'];
attributes_list = latest_smart_result.scsi_attributes
attributes = latest_smart_result.attrs
} else if(this.isNvme()){
this.smartAttributeTableColumns = ['status', 'name', 'value', 'thresh', 'ideal', 'history'];
attributes_list = latest_smart_result.nvme_attributes
attributes = latest_smart_result.attrs
} else {
//ATA
attributes_list = latest_smart_result.ata_attributes
attributes = latest_smart_result.attrs
this.smartAttributeTableColumns = ['status', 'id', 'name', 'value', 'worst', 'thresh','ideal', 'failure', 'history'];
}
for(const attrId in attributes){
var attr = attributes[attrId]
for(let attr of attributes_list){
//chart history data
if (!attr.chartData) {
var rawHistory = (attr.history || []).map(hist_attr => this.getAttributeValue(hist_attr)).reverse()
@@ -235,7 +246,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
}
//determine when to include the attributes in table.
if(!this.onlyCritical || this.onlyCritical && this.data.metadata[attr.attribute_id]?.critical || attr.value < attr.thresh){
if(!this.onlyCritical || this.onlyCritical && this.metadata[attr.attribute_id]?.critical || attr.value < attr.thresh){
smartAttributeDataSource.push(attr)
}
}
@@ -297,7 +308,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
}
toggleOnlyCritical(){
this.onlyCritical = !this.onlyCritical
this.smartAttributeDataSource.data = this._generateSmartAttributeTableDataSource(this.data.data.smart_results);
this.smartAttributeDataSource.data = this._generateSmartAttributeTableDataSource(this.smart_results);
}