Skip to content

Commit 4f55c03

Browse files
Merge pull request strongbox#36 from aosousa/issue-1697
Issue 1697 - Create a dashboard which shows live graphs of the current cpu, ram and some jvm stats
2 parents 6d804cf + ecbb148 commit 4f55c03

23 files changed

+726
-0
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@
4444
"antlr4": "^4.7.1",
4545
"antlr4-autosuggest": "0.0.28",
4646
"antlr4ts": "^0.4.1-alpha.0",
47+
"chart.js": "^2.9.3",
4748
"class-transformer": "~0.2.3",
4849
"core-js": "^2.6.3",
4950
"dayjs": "~1.8.15",
5051
"event-source-polyfill": "^1.0.9",
5152
"fonts-raleway": "0.0.4",
5253
"ionicons": "^4.4.8",
54+
"ng2-charts": "^2.3.0",
5355
"ngx-toastr": "~10.0.4",
5456
"ngx-virtual-scroller": "^3.0.3",
5557
"open-sans-fontface": "^1.4.0",

src/app/app.routing.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ const routes: Routes = [
6262
path: 'admin/logging',
6363
loadChildren: './modules/logging-management/logging-management.module#LoggingManagementModule'
6464
},
65+
{
66+
path: 'admin/dashboard',
67+
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule)
68+
},
6569
{path: '**', component: PageNotFoundComponent}
6670
];
6771

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {NgModule} from '@angular/core';
2+
import {RouterModule, Routes} from '@angular/router';
3+
4+
import {DashboardComponent} from './pages/dashboard/dashboard.component';
5+
import {AuthGuard} from '../core/auth/auth.guard';
6+
7+
const routes: Routes = [
8+
{
9+
path: '',
10+
component: DashboardComponent,
11+
canActivate: [AuthGuard]
12+
}
13+
];
14+
15+
@NgModule({
16+
imports: [RouterModule.forChild(routes)],
17+
exports: [RouterModule]
18+
})
19+
export class DashboardRoutingModule {
20+
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { DashboardModule } from './dashboard.module';
2+
3+
describe('Module: DashboardModule', () => {
4+
let dashboardModule: DashboardModule;
5+
6+
beforeEach(() => {
7+
dashboardModule = new DashboardModule();
8+
});
9+
10+
it('should create an instance', () => {
11+
expect(dashboardModule).toBeTruthy();
12+
});
13+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {NgModule} from '@angular/core';
2+
import {CommonModule} from '@angular/common';
3+
import {ChartsModule} from 'ng2-charts';
4+
5+
import {DashboardComponent} from './pages/dashboard/dashboard.component';
6+
import {DashboardRoutingModule} from './dashboard-routing.module';
7+
import {DashboardMetricsService} from './services/dashboard-metrics.service';
8+
import {MaterialModule} from '../../shared/material.module';
9+
import {LayoutModule} from '../../shared/layout/layout.module';
10+
import {CpuStatsComponent} from './pages/dashboard/components/cpu-stats/cpu-stats.component';
11+
import {JvmMemoryStatsComponent} from './pages/dashboard/components/jvm-memory-stats/jvm-memory-stats.component';
12+
import {JvmLiveThreadsStatsComponent} from './pages/dashboard/components/jvm-live-threads-stats/jvm-live-threads-stats.component';
13+
14+
@NgModule({
15+
imports: [
16+
CommonModule,
17+
18+
LayoutModule,
19+
MaterialModule,
20+
21+
ChartsModule,
22+
23+
DashboardRoutingModule
24+
],
25+
declarations: [
26+
DashboardComponent,
27+
CpuStatsComponent,
28+
JvmMemoryStatsComponent,
29+
JvmLiveThreadsStatsComponent
30+
],
31+
providers: [
32+
DashboardMetricsService
33+
]
34+
})
35+
export class DashboardModule {
36+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<mat-card>
2+
<mat-card-content>
3+
<h3>CPU</h3>
4+
5+
<div class="blue-border">&nbsp;</div>
6+
7+
<mat-progress-spinner mode="indeterminate" strokeWidth="2" *ngIf="loading$ | async"></mat-progress-spinner>
8+
9+
<canvas baseChart width="350" height="200"
10+
[style.display]="(loading$ | async) ? 'none' : ''"
11+
style="margin-top: 1em;"
12+
[datasets]="cpuLineChartDataSet"
13+
[labels]="cpuLineChartLabels"
14+
[options]="cpuLineChartOptions"
15+
[colors]="cpuLineChartColors"
16+
[legend]="cpuLineChartLegend"
17+
[chartType]="cpuLineChartType">
18+
</canvas>
19+
</mat-card-content>
20+
</mat-card>

src/app/modules/dashboard/pages/dashboard/components/cpu-stats/cpu-stats.component.scss

Whitespace-only changes.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import {ComponentFixture, TestBed} from '@angular/core/testing';
2+
import {RouterTestingModule} from '@angular/router/testing';
3+
import {HttpClientTestingModule} from '@angular/common/http/testing';
4+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
5+
import {ToastrModule} from 'ngx-toastr';
6+
import {ChartsModule} from 'ng2-charts';
7+
8+
import {MaterialModule} from '../../../../../../shared/material.module';
9+
10+
import {CpuStatsComponent} from './cpu-stats.component';
11+
12+
describe('CpuStatsComponent', () => {
13+
let component: CpuStatsComponent;
14+
let fixture: ComponentFixture<CpuStatsComponent>;
15+
16+
beforeEach(() => {
17+
TestBed.configureTestingModule({
18+
imports: [
19+
NoopAnimationsModule,
20+
MaterialModule,
21+
HttpClientTestingModule,
22+
RouterTestingModule,
23+
ToastrModule.forRoot(),
24+
ChartsModule
25+
],
26+
declarations: [CpuStatsComponent]
27+
});
28+
29+
fixture = TestBed.createComponent(CpuStatsComponent);
30+
component = fixture.componentInstance;
31+
fixture.detectChanges();
32+
});
33+
34+
it('should create an instance', () => {
35+
expect(component).toBeTruthy();
36+
});
37+
38+
});
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import {Component, OnInit, OnDestroy, ViewChild} from '@angular/core';
2+
import {Color, BaseChartDirective} from 'ng2-charts';
3+
import {BehaviorSubject, Subject, throwError, timer} from 'rxjs';
4+
import {catchError, mergeMap, map, takeUntil, tap} from 'rxjs/operators';
5+
import {ToastrService} from 'ngx-toastr';
6+
7+
import * as chartJs from 'chart.js';
8+
9+
import {DashboardMetricsService} from '../../../../services/dashboard-metrics.service';
10+
11+
@Component({
12+
selector: 'app-cpu-stats-component',
13+
templateUrl: './cpu-stats.component.html',
14+
styleUrls: ['./cpu-stats.component.scss']
15+
})
16+
export class CpuStatsComponent implements OnInit, OnDestroy {
17+
@ViewChild(BaseChartDirective, {static: true})
18+
cpuChart: BaseChartDirective;
19+
20+
loading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
21+
22+
public cpuLineChartData: Array<any> = [0];
23+
public cpuLineChartLabels: Array<any> = [''];
24+
25+
public cpuLineChartOptions: any = {
26+
responsive: true,
27+
scales: {
28+
xAxes: [
29+
{
30+
gridLines: {
31+
display: false
32+
}
33+
}
34+
],
35+
yAxes: [
36+
{
37+
id: 'y-0',
38+
position: 'left',
39+
ticks: {
40+
beginAtZero: true,
41+
max: 100,
42+
min: 0
43+
},
44+
gridLines: {
45+
display: false
46+
}
47+
}
48+
]
49+
}
50+
};
51+
52+
public cpuLineChartColors: Color[] = [
53+
{
54+
backgroundColor: 'rgba(0,0,0,0)',
55+
borderColor: '#2196F3',
56+
pointBackgroundColor: 'rgba(148,159,177,1)',
57+
pointBorderColor: '#fff',
58+
pointHoverBackgroundColor: '#fff',
59+
pointHoverBorderColor: 'rgba(148,159,177,0.8)'
60+
}
61+
];
62+
63+
public cpuLineChartLegend = false;
64+
public cpuLineChartType: chartJs.ChartType = 'line';
65+
66+
public cpuLineChartDataSet: Array<any> = [
67+
{
68+
data: this.cpuLineChartData,
69+
label: 'CPU Usage (%)',
70+
radius: 0
71+
}
72+
];
73+
74+
private destroy$: Subject<any> = new Subject();
75+
76+
constructor(private service: DashboardMetricsService,
77+
private notify: ToastrService) {
78+
}
79+
80+
ngOnInit() {
81+
timer(0, 2000)
82+
.pipe(
83+
mergeMap(() => this.service.getCpuUsage().pipe(takeUntil(this.destroy$))),
84+
map((response: any) => {
85+
let cpuUsage = response.measurements[0].value * 100;
86+
87+
const _lineChartData = this.cpuLineChartData;
88+
const _lineChartLabels = this.cpuLineChartLabels;
89+
90+
if (_lineChartData.length >= 30) {
91+
_lineChartData.shift();
92+
_lineChartLabels.shift();
93+
}
94+
95+
_lineChartData.push(cpuUsage);
96+
97+
// This is just to be able to render the new data added to the chart
98+
_lineChartLabels.push('');
99+
100+
this.cpuLineChartData = _lineChartData;
101+
this.cpuLineChartLabels = _lineChartLabels;
102+
this.cpuChart.chart.update();
103+
}),
104+
catchError((err: any) => {
105+
this.notify.error('Failed to complete request');
106+
console.error(err);
107+
return throwError(err);
108+
}),
109+
tap(() => this.loading$.next(false)),
110+
takeUntil(this.destroy$)
111+
)
112+
.subscribe();
113+
}
114+
115+
ngOnDestroy() {
116+
this.destroy$.next();
117+
this.destroy$.complete();
118+
}
119+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<mat-card>
2+
<mat-card-content>
3+
<h3>JVM Live Threads</h3>
4+
5+
<div class="blue-border">&nbsp;</div>
6+
7+
<mat-progress-spinner mode="indeterminate" strokeWidth="2" *ngIf="loading$ | async"></mat-progress-spinner>
8+
9+
<canvas baseChart width="350" height="200"
10+
[style.display]="(loading$ | async) ? 'none' : ''"
11+
style="margin-top: 1em;"
12+
[datasets]="jvmLiveThreadsLineChartDataSet"
13+
[labels]="jvmLiveThreadsLineChartLabels"
14+
[options]="jvmLiveThreadLineChartOptions"
15+
[colors]="jvmLineChartColors"
16+
[legend]="jvmLineChartLegend"
17+
[chartType]="jvmLineChartType">
18+
</canvas>
19+
</mat-card-content>
20+
</mat-card>

src/app/modules/dashboard/pages/dashboard/components/jvm-live-threads-stats/jvm-live-threads-stats.component.scss

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import {ComponentFixture, TestBed} from '@angular/core/testing';
2+
import {RouterTestingModule} from '@angular/router/testing';
3+
import {HttpClientTestingModule} from '@angular/common/http/testing';
4+
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
5+
import {ChartsModule} from 'ng2-charts';
6+
7+
import {MaterialModule} from '../../../../../../shared/material.module';
8+
9+
import {JvmLiveThreadsStatsComponent} from './jvm-live-threads-stats.component';
10+
11+
describe('JvmLiveThreadsStatsComponent', () => {
12+
let component: JvmLiveThreadsStatsComponent;
13+
let fixture: ComponentFixture<JvmLiveThreadsStatsComponent>;
14+
15+
beforeEach(() => {
16+
TestBed.configureTestingModule({
17+
imports: [
18+
NoopAnimationsModule,
19+
MaterialModule,
20+
HttpClientTestingModule,
21+
RouterTestingModule,
22+
ChartsModule
23+
],
24+
declarations: [JvmLiveThreadsStatsComponent]
25+
});
26+
27+
fixture = TestBed.createComponent(JvmLiveThreadsStatsComponent);
28+
component = fixture.componentInstance;
29+
fixture.detectChanges();
30+
});
31+
32+
it('should create an instance', () => {
33+
expect(component).toBeTruthy();
34+
});
35+
36+
});

0 commit comments

Comments
 (0)