This commit is contained in:
Jason Kulatunga
2020-08-19 16:04:21 -07:00
commit 8482272d45
336 changed files with 197309 additions and 0 deletions
@@ -0,0 +1,146 @@
<div class="flex flex-col flex-auto w-full p-8 xs:p-2">
<div class="flex flex-wrap w-full">
<div class="flex items-center justify-between w-full my-4 px-4 xs:pr-0">
<div class="mr-6">
<h2 class="m-0">Dashboard</h2>
<div class="text-secondary tracking-tight">Drive health at a glance</div>
</div>
<!-- Action buttons -->
<div class="flex items-center">
<button class="xs:hidden"
mat-stroked-button>
<mat-icon class="icon-size-20"
[svgIcon]="'save'"></mat-icon>
<span class="ml-2">Export</span>
</button>
<button class="ml-2 xs:hidden"
mat-stroked-button>
<mat-icon class="icon-size-20 rotate-90 mirror"
[svgIcon]="'tune'"></mat-icon>
<span class="ml-2">Settings</span>
</button>
<!-- Actions menu (visible on xs) -->
<div class="hidden xs:flex">
<button [matMenuTriggerFor]="actionsMenu"
mat-icon-button>
<mat-icon [svgIcon]="'more_vert'"></mat-icon>
</button>
<mat-menu #actionsMenu="matMenu">
<button mat-menu-item>
<mat-icon class="icon-size-20"
[svgIcon]="'save'"></mat-icon>
<span class="ml-2">Export</span>
</button>
<button mat-menu-item>
<mat-icon class="icon-size-20 rotate-90 mirror"
[svgIcon]="'tune'"></mat-icon>
<span class="ml-2">Settings</span>
</button>
</mat-menu>
</div>
</div>
</div>
<div class="flex flex-wrap w-full">
<div *ngFor="let disk of data.data " class="flex flex-auto 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"
*ngIf="disk.smart_results[0]?.smart_status == 'passed'"
[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'"
[svgIcon]="'heroicons_outline:exclamation-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">
Last Updated on {{disk.smart_results[0]?.date | date:'MMMM dd, yyyy' }}
</div>
</div>
<div class="ml-auto" *ngIf="disk.smart_results">
<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">
<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">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>
<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>
<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>
<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">{{ humanizeHours(disk.smart_results[0]?.power_on_hours) }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- Drive Temperatures -->
<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 p-6 pr-4 pb-4">
<div class="flex items-center justify-between">
<div class="flex flex-col">
<div class="font-bold text-md text-secondary uppercase tracking-wider mr-4">Temperature</div>
<div class="text-sm text-hint font-medium">Temperature history for each device </div>
</div>
<div>
<button class="h-8 min-h-8 px-2"
mat-button
[matMenuTriggerFor]="accountBalanceMenu">
<span class="font-medium text-sm text-hint">12 months</span>
</button>
<mat-menu #accountBalanceMenu="matMenu">
<button mat-menu-item>3 months</button>
<button mat-menu-item>6 months</button>
<button mat-menu-item>9 months</button>
<button mat-menu-item>12 months</button>
</mat-menu>
</div>
</div>
</div>
<div class="flex flex-col flex-auto">
<apx-chart class="flex-auto w-full h-full"
[chart]="temperatureOptions.chart"
[colors]="temperatureOptions.colors"
[fill]="temperatureOptions.fill"
[series]="temperatureOptions.series"
[stroke]="temperatureOptions.stroke"
[tooltip]="temperatureOptions.tooltip"
[xaxis]="temperatureOptions.xaxis"></apx-chart>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,22 @@
@import 'treo';
example {
}
// -----------------------------------------------------------------------------------------------------
// @ Theming
// -----------------------------------------------------------------------------------------------------
@include treo-theme {
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$is-dark: map-get($theme, is-dark);
example {
}
}
@@ -0,0 +1,186 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ApexOptions } from 'ng-apexcharts';
import { DashboardService } from 'app/modules/admin/dashboard/dashboard.service';
import * as moment from "moment";
@Component({
selector : 'example',
templateUrl : './dashboard.component.html',
styleUrls : ['./dashboard.component.scss'],
encapsulation : ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent implements OnInit, AfterViewInit, OnDestroy
{
data: any;
temperatureOptions: ApexOptions;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {SmartService} _smartService
*/
constructor(
private _smartService: DashboardService
)
{
// Set the private defaults
this._unsubscribeAll = new Subject();
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Get the data
this._smartService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
// Store the data
this.data = data;
// Prepare the chart data
this._prepareChartData();
});
}
/**
* After view init
*/
ngAfterViewInit(): void
{}
/**
* On destroy
*/
ngOnDestroy(): void
{
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
private _deviceDataTemperatureSeries() {
var deviceTemperatureSeries = []
for(let device of this.data.data){
var deviceSeriesMetadata = {
name: `/dev/${device.device_name}`,
data: []
}
for(let smartResults of device.smart_results){
let newDate = new Date(smartResults.CreatedAt);
deviceSeriesMetadata.data.push({
x: newDate,
y: smartResults.temp
})
}
deviceTemperatureSeries.push(deviceSeriesMetadata)
}
return deviceTemperatureSeries
}
/**
* Prepare the chart data from the data
*
* @private
*/
private _prepareChartData(): void
{
// Account balance
this.temperatureOptions = {
chart : {
animations: {
speed : 400,
animateGradually: {
enabled: false
}
},
fontFamily: 'inherit',
foreColor : 'inherit',
width : '100%',
height : '100%',
type : 'area',
sparkline : {
enabled: true
}
},
colors : ['#A3BFFA', '#667EEA'],
fill : {
colors : ['#CED9FB', '#AECDFD'],
opacity: 0.5,
type : 'solid'
},
series : this._deviceDataTemperatureSeries(),
stroke : {
curve: 'straight',
width: 2
},
tooltip: {
theme: 'dark',
x : {
format: 'MMM dd, yyyy hh:mm:ss'
},
y : {
formatter: (value) => {
return value + '°C';
}
}
},
xaxis : {
type: 'datetime'
}
};
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Track by function for ngFor loops
*
* @param index
* @param item
*/
trackByFn(index: number, item: any): any
{
return item.id || index;
}
humanizeHours(hours: number): string {
if(!hours){
return '--'
}
var value: number
let unit = ""
if(hours > (24*365)){ //more than a year
value = Math.round((hours/(24*365)) * 10)/10
unit = "years"
} else if (hours > 24){
value = Math.round((hours/24) *10 )/10
unit = "days"
} else{
value = hours
unit = "hours"
}
return `${value} ${unit}`
}
}
@@ -0,0 +1,34 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from 'app/shared/shared.module';
import { DashboardComponent } from 'app/modules/admin/dashboard/dashboard.component';
import { dashboardRoutes } from 'app/modules/admin/dashboard/dashboard.routing';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
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';
@NgModule({
declarations: [
DashboardComponent
],
imports : [
RouterModule.forChild(dashboardRoutes),
MatButtonModule,
MatDividerModule,
MatIconModule,
MatMenuModule,
MatProgressBarModule,
MatSortModule,
MatTableModule,
NgApexchartsModule,
SharedModule
]
})
export class DashboardModule
{
}
@@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DashboardService } from 'app/modules/admin/dashboard/dashboard.service';
@Injectable({
providedIn: 'root'
})
export class DashboardResolver implements Resolve<any>
{
/**
* Constructor
*
* @param {FinanceService} _dashboardService
*/
constructor(
private _dashboardService: DashboardService
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Resolver
*
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return this._dashboardService.getData();
}
}
@@ -0,0 +1,13 @@
import { Route } from '@angular/router';
import { DashboardComponent } from 'app/modules/admin/dashboard/dashboard.component';
import {DashboardResolver} from "./dashboard.resolvers";
export const dashboardRoutes: Route[] = [
{
path : '',
component: DashboardComponent,
resolve : {
sales: DashboardResolver
}
}
];
@@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DashboardService
{
// Observables
private _data: BehaviorSubject<any>;
/**
* Constructor
*
* @param {HttpClient} _httpClient
*/
constructor(
private _httpClient: HttpClient
)
{
// Set the private defaults
this._data = new BehaviorSubject(null);
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any>
{
return this._data.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(): Observable<any>
{
return this._httpClient.get('/api/summary').pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
}
@@ -0,0 +1,319 @@
<div class="flex flex-col flex-auto w-full p-8 xs:p-2">
<div class="flex flex-wrap w-full">
<div class="flex items-center justify-between w-full my-4 px-4 xs:pr-0">
<div class="mr-6">
<h2 class="m-0">Drive Details</h2>
<div class="text-secondary tracking-tight">Dive into S.M.A.R.T data</div>
</div>
<!-- Action buttons -->
<div class="flex items-center">
<button class="xs:hidden"
mat-stroked-button>
<mat-icon class="icon-size-20"
[svgIcon]="'save'"></mat-icon>
<span class="ml-2">Export</span>
</button>
<button class="ml-2 xs:hidden"
mat-stroked-button>
<mat-icon class="icon-size-20 rotate-90 mirror"
[svgIcon]="'tune'"></mat-icon>
<span class="ml-2">Settings</span>
</button>
<!-- Actions menu (visible on xs) -->
<div class="hidden xs:flex">
<button [matMenuTriggerFor]="actionsMenu"
mat-icon-button>
<mat-icon [svgIcon]="'more_vert'"></mat-icon>
</button>
<mat-menu #actionsMenu="matMenu">
<button mat-menu-item>
<mat-icon class="icon-size-20"
[svgIcon]="'save'"></mat-icon>
<span class="ml-2">Export</span>
</button>
<button mat-menu-item>
<mat-icon class="icon-size-20 rotate-90 mirror"
[svgIcon]="'tune'"></mat-icon>
<span class="ml-2">Settings</span>
</button>
</mat-menu>
</div>
</div>
</div>
<!-- Card -->
<treo-card class="flex flex-col max-w-80 w-full mt-8 p-4 pt-6 filter-list">
<div class="flex items-center justify-between">
<div class="text-2xl font-semibold leading-tight">/dev/{{data.data.device_name}}</div>
</div>
<div class="flex flex-col my-2">
<div *ngIf="data.data.manufacturer" class="my-2">
<div>{{data.data.manufacturer}}</div>
<div class="text-secondary text-md">Model Family</div>
</div>
<div class="my-2">
<div>{{data.data.model_name}}</div>
<div class="text-secondary text-md">Device Model</div>
</div>
<div class="my-2">
<div>{{data.data.serial_number}}</div>
<div class="text-secondary text-md">Serial Number</div>
</div>
<div class="my-2">
<div>{{data.data.wwn}}</div>
<div class="text-secondary text-md">LU WWN Device Id</div>
</div>
<div class="my-2">
<div>{{data.data.firmware}}</div>
<div class="text-secondary text-md">Firmware Version</div>
</div>
<div class="my-2">
<div>{{data.data.capacity | fileSize}}</div>
<div class="text-secondary text-md">Capacity</div>
</div>
<div *ngIf="data.data.rotational_speed" class="my-2">
<div>{{data.data.rotational_speed}} RPM</div>
<div class="text-secondary text-md">Rotation Rate</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>
</div>
<div class="my-2">
<div matTooltip="{{data.data.smart_results[0]?.power_on_hours}} hours">{{humanizeHours(data.data.smart_results[0]?.power_on_hours)}}</div>
<div class="text-secondary text-md">Powered On</div>
</div>
<div class="my-2">
<div>{{data.data.smart_results[0]?.temp}}°C</div>
<div class="text-secondary text-md">Temperature</div>
</div>
</div>
</treo-card>
<!-- S.M.A.R.T. Data table -->
<div class="flex flex-auto w-1/3 p-8 lt-xl:w-full">
<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>
<div class="overflow-auto">
<table class="w-full bg-transparent"
mat-table
matSort
[dataSource]="smartAttributeDataSource"
[trackBy]="trackByFn"
#smartAttributeTable>
<!-- Status -->
<ng-container matColumnDef="status">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Status
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="inline-flex items-center font-bold text-xs px-2 py-2px rounded-full tracking-wide uppercase"
[ngClass]="{'red-200': attribute.status === 'failed',
'green-200': attribute.status === 'passed',
'yellow-200': attribute.status === 'warn'
}">
<span class="w-2 h-2 rounded-full mr-2"
[ngClass]="{'bg-red': attribute.status === 'failed',
'bg-green': attribute.status === 'passed',
'bg-yellow': attribute.status === 'warn'}"></span>
<span class="pr-2px leading-relaxed whitespace-no-wrap" matTooltip="{{attribute.status_reason}}">{{attribute.status}}</span>
</span>
</td>
</ng-container>
<!-- ID -->
<ng-container matColumnDef="id">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
ID
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="pr-6 font-medium text-sm text-secondary whitespace-no-wrap">
{{attribute.attribute_id}} ({{toHex(attribute.attribute_id)}})
</span>
</td>
</ng-container>
<!-- Name -->
<ng-container matColumnDef="name">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Name
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="pr-6 whitespace-no-wrap" matTooltip="{{data.lookup[attribute.attribute_id]?.description}}">
{{attribute.name}} <mat-icon class="icon-size-10" [svgIcon]="'info'"></mat-icon>
</span>
</td>
</ng-container>
<!-- Value -->
<ng-container matColumnDef="value">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Value
</span>
</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>
</td>
</ng-container>
<!-- Worst -->
<ng-container matColumnDef="worst">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Worst
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="pr-6 whitespace-no-wrap">
{{attribute.worst}}
</span>
</td>
</ng-container>
<!-- Threshold -->
<ng-container matColumnDef="thresh">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Threshold
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="pr-6 whitespace-no-wrap">
{{attribute.thresh}}
</span>
</td>
</ng-container>
<!-- Ideal -->
<ng-container matColumnDef="ideal">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Ideal
</span>
</th>
<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 : '' }}
</span>
</td>
</ng-container>
<!-- Observed Failure Rate -->
<ng-container matColumnDef="failure">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
Failure Rate <mat-icon [svgIcon]="'info'"></mat-icon>
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="pr-6 font-medium whitespace-no-wrap">
{{attribute.failure_rate | percent}}
</span>
</td>
</ng-container>
<!-- History -->
<ng-container matColumnDef="history">
<th class="bg-cool-gray-50 dark:bg-cool-gray-700 border-t"
mat-header-cell
mat-sort-header
*matHeaderCellDef>
<span class="whitespace-no-wrap">
History
</span>
</th>
<td mat-cell
*matCellDef="let attribute">
<span class="font-medium whitespace-no-wrap">
<apx-chart
[series]="attribute.chartData"
[chart]="commonSparklineOptions.chart"
[tooltip]="commonSparklineOptions.tooltip"
[stroke]="commonSparklineOptions.stroke"
[annotations]="attribute.chartDataReferenceLine"
></apx-chart>
</span>
</td>
</ng-container>
<!-- Footer -->
<ng-container matColumnDef="recentOrdersTableFooter">
<td class="px-3 border-none"
mat-footer-cell
*matFooterCellDef
colspan="6">
<button mat-button
(click)="toggleOnlyCritical()"
[color]="'primary'">
<span *ngIf="onlyCritical">Show all attributes</span>
<span *ngIf="!onlyCritical">Show critical attributes</span>
</button>
</td>
</ng-container>
<tr mat-header-row
*matHeaderRowDef="smartAttributeTableColumns"></tr>
<tr class="attribute-row h-16"
mat-row
[ngClass]="{'yellow-50': data.lookup[row.attribute_id]?.critical}"
*matRowDef="let row; columns: smartAttributeTableColumns;"></tr>
<tr class="h-16"
mat-footer-row
*matFooterRowDef="['recentOrdersTableFooter']"></tr>
</table>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,24 @@
@import 'treo';
detail {
}
// -----------------------------------------------------------------------------------------------------
// @ Theming
// -----------------------------------------------------------------------------------------------------
@include treo-theme {
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$is-dark: map-get($theme, is-dark);
detail {
}
}
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DetailComponent } from './detail.component';
describe('DetailComponent', () => {
let component: DetailComponent;
let fixture: ComponentFixture<DetailComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DetailComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,246 @@
import {AfterViewInit, Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
import {ApexOptions} from "ng-apexcharts";
import {MatTableDataSource} from "@angular/material/table";
import {MatSort} from "@angular/material/sort";
import {Subject} from "rxjs";
import {DetailService} from "../detail/detail.service";
import {takeUntil} from "rxjs/operators";
import {fadeOut} from "../../../../@treo/animations/fade";
@Component({
selector: 'detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss']
})
export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
onlyCritical: boolean = true;
data: any;
commonSparklineOptions: Partial<ApexOptions>;
smartAttributeDataSource: MatTableDataSource<any>;
smartAttributeTableColumns: string[];
@ViewChild('smartAttributeTable', {read: MatSort})
smartAttributeTableMatSort: MatSort;
// Private
private _unsubscribeAll: Subject<any>;
/**
* Constructor
*
* @param {DetailService} _detailService
*/
constructor(
private _detailService: DetailService
)
{
// Set the private defaults
this._unsubscribeAll = new Subject();
// Set the defaults
this.smartAttributeDataSource = new MatTableDataSource();
// this.recentTransactionsTableColumns = ['status', 'id', 'name', 'value', 'worst', 'thresh'];
this.smartAttributeTableColumns = ['status', 'id', 'name', 'value', 'worst', 'thresh','ideal', 'failure', 'history'];
}
// -----------------------------------------------------------------------------------------------------
// @ Lifecycle hooks
// -----------------------------------------------------------------------------------------------------
/**
* On init
*/
ngOnInit(): void
{
// Get the data
this._detailService.data$
.pipe(takeUntil(this._unsubscribeAll))
.subscribe((data) => {
// Store the data
this.data = data;
// Store the table data
this.smartAttributeDataSource.data = this._generateSmartAttributeTableDataSource(data.data.smart_results);
// Prepare the chart data
this._prepareChartData();
});
}
/**
* After view init
*/
ngAfterViewInit(): void
{
// Make the data source sortable
this.smartAttributeDataSource.sort = this.smartAttributeTableMatSort;
}
/**
* On destroy
*/
ngOnDestroy(): void
{
// Unsubscribe from all subscriptions
this._unsubscribeAll.next();
this._unsubscribeAll.complete();
}
// -----------------------------------------------------------------------------------------------------
// @ Private methods
// -----------------------------------------------------------------------------------------------------
extractAttributeValue(attribute_metadata, attribute_data){
if(attribute_metadata.display_type == "raw"){
return attribute_data.raw_value
}
else if(attribute_metadata.display_type == "transformed" && attribute_data.transformed_value ){
return attribute_data.transformed_value
} else {
return attribute_data.value
}
}
private _generateSmartAttributeTableDataSource(smart_results){
var smartAttributeDataSource = [];
if(smart_results.length == 0){
return smartAttributeDataSource
}
var latest_smart_result = smart_results[0];
for(let attr of latest_smart_result.smart_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))
attr.chartData = [
{
name: "chart-line-sparkline",
// data: Array.from({length: 40}, () => Math.floor(Math.random() * 40))
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){
smartAttributeDataSource.push(attr)
}
}
return smartAttributeDataSource
}
/**
* Prepare the chart data from the data
*
* @private
*/
private _prepareChartData(): void
{
// Account balance
this.commonSparklineOptions = {
chart: {
type: "bar",
width: 100,
height: 25,
sparkline: {
enabled: true
},
animations: {
enabled: false
}
},
tooltip: {
fixed: {
enabled: false
},
x: {
show: false
},
y: {
title: {
formatter: function(seriesName) {
return "";
}
}
},
marker: {
show: false
}
},
stroke: {
width: 2,
colors: ['#667EEA']
}
};
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
toHex(decimalNumb){
return "0x" + Number(decimalNumb).toString(16).padStart(2, '0').toUpperCase()
}
toggleOnlyCritical(){
this.onlyCritical = !this.onlyCritical
this.smartAttributeDataSource.data = this._generateSmartAttributeTableDataSource(this.data.data.smart_results);
}
/**
* Track by function for ngFor loops
*
* @param index
* @param item
*/
trackByFn(index: number, item: any): any
{
return index;
// return item.id || index;
}
humanizeHours(hours: number): string {
if(!hours){
return '--'
}
var value: number
let unit = ""
if(hours > (24*365)){ //more than a year
value = Math.round((hours/(24*365)) * 10)/10
unit = "years"
} else if (hours > 24){
value = Math.round((hours/24) *10 )/10
unit = "days"
} else{
value = hours
unit = "hours"
}
return `${value} ${unit}`
}
}
@@ -0,0 +1,39 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { SharedModule } from 'app/shared/shared.module';
import { DetailComponent } from 'app/modules/admin/detail/detail.component';
import { detailRoutes } from 'app/modules/admin/detail/detail.routing';
import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
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 { MatTooltipModule } from '@angular/material/tooltip'
import { NgApexchartsModule } from 'ng-apexcharts';
import { TreoCardModule } from '@treo/components/card';
@NgModule({
declarations: [
DetailComponent
],
imports : [
RouterModule.forChild(detailRoutes),
MatButtonModule,
MatDividerModule,
MatTooltipModule,
MatIconModule,
MatMenuModule,
MatProgressBarModule,
MatSortModule,
MatTableModule,
NgApexchartsModule,
TreoCardModule,
SharedModule,
]
})
export class DetailModule
{
}
@@ -0,0 +1,36 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { DetailService } from 'app/modules/admin/detail/detail.service';
@Injectable({
providedIn: 'root'
})
export class DetailResolver implements Resolve<any>
{
/**
* Constructor
*
* @param {FinanceService} _detailService
*/
constructor(
private _detailService: DetailService
)
{
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Resolver
*
* @param route
* @param state
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any>
{
return this._detailService.getData(route.params.wwn);
}
}
@@ -0,0 +1,13 @@
import { Route } from '@angular/router';
import { DetailComponent } from 'app/modules/admin/detail/detail.component';
import {DetailResolver} from "./detail.resolvers";
export const detailRoutes: Route[] = [
{
path : '',
component: DetailComponent,
resolve : {
sales: DetailResolver
}
}
];
@@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class DetailService
{
// Observables
private _data: BehaviorSubject<any>;
/**
* Constructor
*
* @param {HttpClient} _httpClient
*/
constructor(
private _httpClient: HttpClient
)
{
// Set the private defaults
this._data = new BehaviorSubject(null);
}
// -----------------------------------------------------------------------------------------------------
// @ Accessors
// -----------------------------------------------------------------------------------------------------
/**
* Getter for data
*/
get data$(): Observable<any>
{
return this._data.asObservable();
}
// -----------------------------------------------------------------------------------------------------
// @ Public methods
// -----------------------------------------------------------------------------------------------------
/**
* Get data
*/
getData(wwn): Observable<any>
{
return this._httpClient.get(`/api/device/${wwn}/details`).pipe(
tap((response: any) => {
this._data.next(response);
})
);
}
}
@@ -0,0 +1,21 @@
<div class="flex flex-col items-center justify-center w-full h-full cool-gray-900">
<div class="flex flex-col items-center max-w-lg rich-text">
<img class="w-20"
src="assets/images/logo/scrutiny-logo-white.svg">
<h2>Landing Module</h2>
<p>
This can be the landing or the welcome page of your application. If you have an SSR (Server Side Rendering) setup, or if you don't need to have Search engine
visibility and optimizations, you can even use this page as your primary landing page.
</p>
<p>
This is a separate "module", it has its own directory and routing setup and also it's completely separated from the actual application. This is also a simple example of
multiple applications setup. You can have different entry points and essentially have different applications within the same codebase.
</p>
<a class="mt-10 no-underline"
mat-raised-button
[color]="'primary'"
[routerLink]="'/apps/analytics-dashboard'">
Launch the App
</a>
</div>
</div>
@@ -0,0 +1,12 @@
@import 'treo';
landing-home {
}
// -----------------------------------------------------------------------------------------------------
// @ Theming
// -----------------------------------------------------------------------------------------------------
@include treo-theme {
}
@@ -0,0 +1,17 @@
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector : 'landing-home',
templateUrl : './home.component.html',
styleUrls : ['./home.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class LandingHomeComponent
{
/**
* Constructor
*/
constructor()
{
}
}
@@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MatButtonModule } from '@angular/material/button';
import { SharedModule } from 'app/shared/shared.module';
import { LandingHomeComponent } from 'app/modules/landing/home/home.component';
import { landingHomeRoutes } from 'app/modules/landing/home/home.routing';
@NgModule({
declarations: [
LandingHomeComponent
],
imports : [
RouterModule.forChild(landingHomeRoutes),
MatButtonModule,
SharedModule
]
})
export class LandingHomeModule
{
}
@@ -0,0 +1,9 @@
import { Route } from '@angular/router';
import { LandingHomeComponent } from 'app/modules/landing/home/home.component';
export const landingHomeRoutes: Route[] = [
{
path : '',
component: LandingHomeComponent
}
];