|
|
@@ -1,18 +1,11 @@
|
|
|
import { Component, Input, OnInit } from '@angular/core';
|
|
|
import { ChartData, ChartOptions, TooltipItem } from 'chart.js';
|
|
|
+import { debounceTime, throttleTime, Subject } from 'rxjs';
|
|
|
|
|
|
import { BytePipe } from 'src/app/pipes/byte.pipe';
|
|
|
import { ServerApiService } from 'src/app/services/server-api.service';
|
|
|
-import { deepCopy } from '../../../../../common/util/object-utils';
|
|
|
|
|
|
type ChartConfigTimeUnit = false | 'millisecond' | 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year' | undefined;
|
|
|
-type ServerDataGraphOptions = ServerDataTypesConfig & {
|
|
|
- subtypes?: Array<ServerDataGraphOptions>;
|
|
|
- data?: ChartData<'line', ServerData[], Date>;
|
|
|
- options: ChartOptions<'line'>;
|
|
|
- start: Date;
|
|
|
- end: Date;
|
|
|
-};
|
|
|
|
|
|
const typeDotColors: { [k: string]: string } = {
|
|
|
avg: '#cc9a06',
|
|
|
@@ -32,40 +25,42 @@ const typeBgColors: { [k: string]: string } = {
|
|
|
styleUrls: ['./server-data-chart.component.scss']
|
|
|
})
|
|
|
export class ServerDataChartComponent implements OnInit {
|
|
|
- private _type!: ServerDataGraphOptions;
|
|
|
+ private zoomEvent$ = new Subject<void>();
|
|
|
+ private dragEvent$ = new Subject<DragEvent>();
|
|
|
+ private _type!: string;
|
|
|
|
|
|
@Input() parent?: string;
|
|
|
@Input() server!: ServerConfig;
|
|
|
- @Input() set type(type: ServerDataTypesConfig) {
|
|
|
- if (!this._type) {
|
|
|
- const end = new Date();
|
|
|
- const start = new Date(end.getTime() - 1000 * 60 * 60 * 4);
|
|
|
- this._type = deepCopy({
|
|
|
- ...type,
|
|
|
- end,
|
|
|
- start
|
|
|
- }) as ServerDataGraphOptions;
|
|
|
- } else {
|
|
|
- this._type.type = type.type;
|
|
|
- this._type.subtypes = type.subtypes as ServerDataGraphOptions[];
|
|
|
- }
|
|
|
+ @Input() set type(type: string) {
|
|
|
+ this._type = type;
|
|
|
}
|
|
|
|
|
|
- public get type(): ServerDataGraphOptions {
|
|
|
+ public get type(): string {
|
|
|
return this._type;
|
|
|
}
|
|
|
|
|
|
- constructor(private apiService: ServerApiService, private bytePipe: BytePipe) {}
|
|
|
+ public start!: Date;
|
|
|
+ public end!: Date;
|
|
|
+ public data?: ChartData<'line', ServerData[], Date>;
|
|
|
+ public options!: ChartOptions<'line'> & any;
|
|
|
+
|
|
|
+ constructor(private apiService: ServerApiService, private bytePipe: BytePipe) {
|
|
|
+ this.defaults();
|
|
|
+ this.zoomEvent$.pipe(debounceTime(400)).subscribe({ next: this.updateData.bind(this) });
|
|
|
+ this.dragEvent$.pipe(throttleTime(400)).subscribe({ next: this.dragDebounced.bind(this) });
|
|
|
+ }
|
|
|
|
|
|
ngOnInit(): void {
|
|
|
this.updateData();
|
|
|
}
|
|
|
|
|
|
- private options(type: ServerDataGraphOptions): ChartOptions<'line'> & any {
|
|
|
- const end = type.end;
|
|
|
- const start = type.start;
|
|
|
+ private defaults() {
|
|
|
+ this.end = new Date();
|
|
|
+ this.start = new Date(this.end.getTime() - 1000 * 60 * 60 * 4);
|
|
|
+ }
|
|
|
|
|
|
- const diffHrs = (end.getTime() - start.getTime()) / 1000 / 60 / 60;
|
|
|
+ private updateOptions() {
|
|
|
+ const diffHrs = (this.end.getTime() - this.start.getTime()) / 1000 / 60 / 60;
|
|
|
const timeFormat: {
|
|
|
tooltipFormat: string;
|
|
|
unit: ChartConfigTimeUnit;
|
|
|
@@ -94,7 +89,7 @@ export class ServerDataChartComponent implements OnInit {
|
|
|
unit: 'minute',
|
|
|
stepSize: 30
|
|
|
};
|
|
|
- return {
|
|
|
+ this.options = {
|
|
|
scales: {
|
|
|
xAxis: {
|
|
|
display: true,
|
|
|
@@ -106,13 +101,15 @@ export class ServerDataChartComponent implements OnInit {
|
|
|
hour: 'yyyy-MM-dd HH:mm',
|
|
|
minute: 'HH:mm:ss'
|
|
|
}
|
|
|
- }
|
|
|
+ },
|
|
|
+ min: this.start,
|
|
|
+ max: this.end
|
|
|
},
|
|
|
yAxis: {
|
|
|
beginAtZero: true,
|
|
|
type: 'linear',
|
|
|
ticks: {
|
|
|
- callback: (val: string | number) => (typeof val === 'number' && type.type !== 'cpu' ? this.bytePipe.transform(val) : `${val} %`)
|
|
|
+ callback: (val: string | number) => (typeof val === 'number' && this.type !== 'cpu' ? this.bytePipe.transform(val) : `${val} %`)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -121,13 +118,11 @@ export class ServerDataChartComponent implements OnInit {
|
|
|
|
|
|
private async updateData() {
|
|
|
try {
|
|
|
- const end = this.type.end;
|
|
|
- const start = this.type.start;
|
|
|
const data = await this.apiService.queryServerData(
|
|
|
this.server.id,
|
|
|
- `${this.parent ?? this.type.type}${this.parent ? `:${this.type.type}` : ''}`,
|
|
|
- start,
|
|
|
- end
|
|
|
+ `${this.parent ?? this.type}${this.parent ? `:${this.type}` : ''}`,
|
|
|
+ this.start,
|
|
|
+ this.end
|
|
|
);
|
|
|
const chartData: ChartData<'line', ServerData[], Date> = {
|
|
|
labels: data.map(d => d.time),
|
|
|
@@ -148,7 +143,7 @@ export class ServerDataChartComponent implements OnInit {
|
|
|
tooltip: {
|
|
|
callbacks: {
|
|
|
label:
|
|
|
- this.type.type !== 'cpu'
|
|
|
+ this.type !== 'cpu'
|
|
|
? (item: TooltipItem<'line'> & any) => this.bytePipe.transform(item.raw[key])
|
|
|
: (item: TooltipItem<'line'> & any) => `${(item.raw[key] as number).toFixed(2)} %`
|
|
|
}
|
|
|
@@ -157,10 +152,107 @@ export class ServerDataChartComponent implements OnInit {
|
|
|
: []
|
|
|
};
|
|
|
|
|
|
- this.type.options = this.options(this.type);
|
|
|
- this.type.data = chartData;
|
|
|
+ this.updateOptions();
|
|
|
+ this.data = chartData;
|
|
|
} catch (err) {
|
|
|
console.error(err);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ zoom(ev: Event) {
|
|
|
+ const event = ev as WheelEvent;
|
|
|
+ if (event.shiftKey) {
|
|
|
+ event.stopPropagation();
|
|
|
+ event.preventDefault();
|
|
|
+ event.cancelBubble = true;
|
|
|
+
|
|
|
+ const xRel = event.offsetX / (event.target as HTMLCanvasElement).clientWidth;
|
|
|
+ const nowMs = new Date().getTime();
|
|
|
+ const currentRangeMs = this.end.getTime() - this.start.getTime();
|
|
|
+ const cursorTimeMs = this.start.getTime() + currentRangeMs * xRel;
|
|
|
+ const step = event.deltaY / 10;
|
|
|
+ const newRangeMs = currentRangeMs * (1 + 1 / step);
|
|
|
+
|
|
|
+ const newStartMs = Math.min(cursorTimeMs - newRangeMs * xRel, nowMs - newRangeMs);
|
|
|
+ const newEndMs = newStartMs + newRangeMs;
|
|
|
+
|
|
|
+ this.start = new Date(newStartMs);
|
|
|
+ this.end = new Date(newEndMs);
|
|
|
+
|
|
|
+ this.updateOptions();
|
|
|
+ this.zoomEvent$.next();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private dragStartParams = {
|
|
|
+ offsetX: 0,
|
|
|
+ rangeStart: 0,
|
|
|
+ rangeEnd: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ dragStart(ev: Event) {
|
|
|
+ const event = ev as DragEvent;
|
|
|
+
|
|
|
+ console.log('[DRAGSTART]', {
|
|
|
+ offsetX: event.offsetX,
|
|
|
+ clientWidth: (event.target as HTMLCanvasElement).clientWidth,
|
|
|
+ xRel: ((event.offsetX / (event.target as HTMLCanvasElement).clientWidth) * 100).toFixed(2)
|
|
|
+ });
|
|
|
+ this.dragStartParams = {
|
|
|
+ offsetX: event.offsetX,
|
|
|
+ rangeStart: this.start.getTime(),
|
|
|
+ rangeEnd: this.end.getTime()
|
|
|
+ };
|
|
|
+
|
|
|
+ event.stopPropagation();
|
|
|
+ // event.preventDefault();
|
|
|
+ event.cancelBubble = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ drag(ev: Event) {
|
|
|
+ const event = ev as DragEvent;
|
|
|
+ event.preventDefault();
|
|
|
+ event.stopPropagation();
|
|
|
+ event.cancelBubble = true;
|
|
|
+
|
|
|
+ console.log('[DRAG]');
|
|
|
+ this.dragEvent$.next(event);
|
|
|
+ }
|
|
|
+
|
|
|
+ dragDebounced(event: DragEvent) {
|
|
|
+ const nowMs = new Date().getTime();
|
|
|
+ const xDiffRel = (event.offsetX - this.dragStartParams.offsetX) / (event.target as HTMLCanvasElement).clientWidth;
|
|
|
+ const currentRangeMs = this.dragStartParams.rangeEnd - this.dragStartParams.rangeStart;
|
|
|
+ const xDiff = xDiffRel * currentRangeMs;
|
|
|
+ const newStartMs = Math.min(this.dragStartParams.rangeStart - xDiff, nowMs - currentRangeMs);
|
|
|
+ const newEndMs = newStartMs + currentRangeMs;
|
|
|
+
|
|
|
+ if (this.start.getTime() !== newStartMs) {
|
|
|
+ this.start = new Date(newStartMs);
|
|
|
+ this.end = new Date(newEndMs);
|
|
|
+
|
|
|
+ console.log('[DRAGDEBOUNCE]', {
|
|
|
+ offsetX: event.offsetX,
|
|
|
+ clientWidth: (event.target as HTMLCanvasElement).clientWidth,
|
|
|
+ xDiffRel,
|
|
|
+ newStart: this.start,
|
|
|
+ newEnd: this.end
|
|
|
+ });
|
|
|
+
|
|
|
+ this.updateOptions();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ dragEnd(ev: Event) {
|
|
|
+ const event = ev as DragEvent;
|
|
|
+
|
|
|
+ console.log('[DRAGEND]', {
|
|
|
+ offsetX: event.offsetX,
|
|
|
+ clientWidth: (event.target as HTMLCanvasElement).clientWidth,
|
|
|
+ xRel: (((event.offsetX - this.dragStartParams.offsetX) / (event.target as HTMLCanvasElement).clientWidth) * 100).toFixed(2)
|
|
|
+ });
|
|
|
+
|
|
|
+ this.updateOptions();
|
|
|
+ this.zoomEvent$.next();
|
|
|
+ }
|
|
|
}
|