|
|
@@ -1,9 +1,35 @@
|
|
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
|
|
import { ActivationEnd, Router } from '@angular/router';
|
|
|
+import { ChartData, ChartOptions, TooltipItem } from 'chart.js';
|
|
|
+import { _DeepPartialObject } from 'chart.js/types/utils';
|
|
|
+import 'chartjs-adapter-date-fns';
|
|
|
import { filter, map, Subscription } from 'rxjs';
|
|
|
|
|
|
+import { deepCopy } from '../../../../../common/util/object-utils';
|
|
|
+
|
|
|
+import { BytePipe } from 'src/app/pipes/byte.pipe';
|
|
|
import { ServerApiService } from 'src/app/services/server-api.service';
|
|
|
|
|
|
+type ServerDataGraphOptions = ServerDataTypesConfig & {
|
|
|
+ subtypes?: Array<ServerDataGraphOptions>;
|
|
|
+ data?: ChartData<'line', ServerData[], Date>;
|
|
|
+ options: ChartOptions<'line'>;
|
|
|
+ start: Date;
|
|
|
+ end: Date;
|
|
|
+};
|
|
|
+type ChartConfigTimeUnit = false | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | undefined;
|
|
|
+
|
|
|
+const typeDotColors: { [k: string]: string } = {
|
|
|
+ avg: '#cc9a06',
|
|
|
+ peak: '#005299',
|
|
|
+ max: '#941320'
|
|
|
+};
|
|
|
+
|
|
|
+const typeBgColors: { [k: string]: string } = {
|
|
|
+ avg: '#ffe69c',
|
|
|
+ peak: '#cce7ff',
|
|
|
+ max: '#e3a3a9'
|
|
|
+};
|
|
|
@Component({
|
|
|
selector: 'app-server-data-page',
|
|
|
templateUrl: './server-data-page.component.html',
|
|
|
@@ -13,15 +39,18 @@ export class ServerDataPageComponent implements OnInit, OnDestroy {
|
|
|
private subscriptions: Subscription[] = [];
|
|
|
|
|
|
public server?: ServerConfig;
|
|
|
- public types?: ServerDataTypeWithData[];
|
|
|
+ public types?: ServerDataGraphOptions[];
|
|
|
|
|
|
- constructor(private apiService: ServerApiService, router: Router) {
|
|
|
+ constructor(private apiService: ServerApiService, router: Router, private bytePipe: BytePipe) {
|
|
|
router.events.subscribe({
|
|
|
next: event => {
|
|
|
if (event instanceof ActivationEnd) {
|
|
|
this.clearSubscriptions();
|
|
|
|
|
|
const serverID = Number(event.snapshot.params['id']);
|
|
|
+ if (serverID !== this.server?.id) {
|
|
|
+ this.clearPageModel();
|
|
|
+ }
|
|
|
this.subscriptions.push(
|
|
|
apiService.serverConfigs$.pipe(map(servers => servers.find(s => s.id === serverID))).subscribe(this.onServerConfig.bind(this)),
|
|
|
apiService.serverDataTypes$
|
|
|
@@ -37,34 +66,137 @@ export class ServerDataPageComponent implements OnInit, OnDestroy {
|
|
|
ngOnInit(): void {}
|
|
|
|
|
|
onServerConfig(server?: ServerConfig) {
|
|
|
- if (server) {
|
|
|
- this.server = server;
|
|
|
- } else {
|
|
|
- this.server = undefined;
|
|
|
- }
|
|
|
+ this.server = server;
|
|
|
}
|
|
|
|
|
|
- onServerDataTypes(types: ServerDataTypeWithData[]) {
|
|
|
+ onServerDataTypes(types: ServerDataTypesConfig[]) {
|
|
|
if (this.server) {
|
|
|
- this.types = types.map(type => {
|
|
|
- if (!type.subtypes) this.updateData(this.server as ServerConfig, type);
|
|
|
- else {
|
|
|
- type.subtypes.forEach(sub => {
|
|
|
+ const end = new Date();
|
|
|
+ const start = new Date(end.getTime() - 1000 * 60 * 60 * 4);
|
|
|
+ this.types = deepCopy(types).map((origType: ServerDataTypeWithData) => {
|
|
|
+ const type = origType as ServerDataGraphOptions;
|
|
|
+ const oldType = this.types?.find(t => type.type === t.type);
|
|
|
+ if (!type.subtypes) {
|
|
|
+ if (oldType && oldType.data) {
|
|
|
+ type.data = oldType.data;
|
|
|
+ return type;
|
|
|
+ }
|
|
|
+ type.start = start;
|
|
|
+ type.end = end;
|
|
|
+ this.updateData(this.server as ServerConfig, type);
|
|
|
+ } else {
|
|
|
+ type.subtypes.forEach((origSub: ServerDataTypeWithData) => {
|
|
|
+ const sub = origSub as ServerDataGraphOptions;
|
|
|
+ if (oldType) {
|
|
|
+ const oldSub = oldType.subtypes?.find(s => sub.type === s.type) as ServerDataGraphOptions;
|
|
|
+ if (oldSub && oldSub.data) {
|
|
|
+ sub.data = oldSub.data;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sub.start = start;
|
|
|
+ sub.end = end;
|
|
|
this.updateData(this.server as ServerConfig, type, sub);
|
|
|
});
|
|
|
}
|
|
|
- return type;
|
|
|
+ return type as ServerDataGraphOptions;
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- async updateData(server: ServerConfig, type: ServerDataTypeWithData, subType?: ServerDataTypeWithData) {
|
|
|
+ async updateData(server: ServerConfig, type: ServerDataGraphOptions, subType?: ServerDataGraphOptions) {
|
|
|
try {
|
|
|
- const end = new Date();
|
|
|
- const start = new Date(end.getTime() - 1000 * 60 * 60 * 4);
|
|
|
+ const end = subType ? subType.end : type.end;
|
|
|
+ const start = subType ? subType.start : type.start;
|
|
|
+
|
|
|
+ const diffHrs = (end.getTime() - start.getTime()) / 1000 / 60 / 60;
|
|
|
+ const timeFormat: {
|
|
|
+ tooltipFormat: string;
|
|
|
+ unit: ChartConfigTimeUnit;
|
|
|
+ stepSize: number;
|
|
|
+ } =
|
|
|
+ diffHrs > 24 * 4
|
|
|
+ ? {
|
|
|
+ tooltipFormat: 'yyyy-MM-dd HH:mm',
|
|
|
+ unit: 'day',
|
|
|
+ stepSize: 1
|
|
|
+ }
|
|
|
+ : diffHrs > 24 * 2
|
|
|
+ ? {
|
|
|
+ tooltipFormat: 'yyyy-MM-dd HH:mm',
|
|
|
+ unit: 'hour',
|
|
|
+ stepSize: 6
|
|
|
+ }
|
|
|
+ : diffHrs > 12
|
|
|
+ ? {
|
|
|
+ tooltipFormat: 'HH:mm:ss',
|
|
|
+ unit: 'hour',
|
|
|
+ stepSize: 1
|
|
|
+ }
|
|
|
+ : {
|
|
|
+ tooltipFormat: 'HH:mm:ss',
|
|
|
+ unit: 'minute',
|
|
|
+ stepSize: 30
|
|
|
+ };
|
|
|
+
|
|
|
const data = await this.apiService.queryServerData(server.id, `${type.type}${subType ? `:${subType.type}` : ''}`, start, end);
|
|
|
- if (subType) subType.data = data;
|
|
|
- else type.data = data;
|
|
|
+ const chartData: ChartData<'line', ServerData[], Date> = {
|
|
|
+ labels: data.map(d => d.time),
|
|
|
+ datasets: data.length
|
|
|
+ ? Object.keys(data[0])
|
|
|
+ .filter(k => k !== 'time')
|
|
|
+ .map(key => ({
|
|
|
+ label: key,
|
|
|
+ data,
|
|
|
+ parsing: { yAxisKey: key, xAxisKey: 'time' },
|
|
|
+ fill: key !== 'max',
|
|
|
+ tension: 0.3,
|
|
|
+ backgroundColor: typeBgColors[key],
|
|
|
+ pointBorderColor: typeDotColors[key],
|
|
|
+ borderColor: typeDotColors[key],
|
|
|
+ pointBackgroundColor: typeBgColors[key],
|
|
|
+ pointRadius: key === 'max' ? 0 : undefined,
|
|
|
+ tooltip: {
|
|
|
+ callbacks: {
|
|
|
+ label:
|
|
|
+ type.type !== 'cpu'
|
|
|
+ ? (item: TooltipItem<'line'> & any) => this.bytePipe.transform(item.raw[key])
|
|
|
+ : (item: TooltipItem<'line'> & any) => `${(item.raw[key] as number).toFixed(2)} %`
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ : []
|
|
|
+ };
|
|
|
+ const options: ChartOptions<'line'> & any = {
|
|
|
+ scales: {
|
|
|
+ xAxis: {
|
|
|
+ display: true,
|
|
|
+ type: 'time',
|
|
|
+ time: {
|
|
|
+ ...timeFormat,
|
|
|
+ displayFormats: {
|
|
|
+ day: 'yyyy-MM-dd',
|
|
|
+ hour: 'yyyy-MM-dd HH:mm',
|
|
|
+ minute: 'HH:mm:ss'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ yAxis: {
|
|
|
+ beginAtZero: true,
|
|
|
+ type: 'linear',
|
|
|
+ ticks: {
|
|
|
+ callback: (val: string | number) => (typeof val === 'number' && type.type !== 'cpu' ? this.bytePipe.transform(val) : `${val} %`)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ if (subType) {
|
|
|
+ subType.data = chartData;
|
|
|
+ subType.options = options;
|
|
|
+ } else {
|
|
|
+ type.data = chartData;
|
|
|
+ type.options = options;
|
|
|
+ }
|
|
|
} catch (err) {
|
|
|
console.error(err);
|
|
|
}
|
|
|
@@ -78,4 +210,9 @@ export class ServerDataPageComponent implements OnInit, OnDestroy {
|
|
|
this.subscriptions.forEach(s => s.unsubscribe());
|
|
|
this.subscriptions = [];
|
|
|
}
|
|
|
+
|
|
|
+ clearPageModel() {
|
|
|
+ this.server = undefined;
|
|
|
+ this.types = undefined;
|
|
|
+ }
|
|
|
}
|