Browse Source

usertime played graphs

master
gsd 4 weeks ago
parent
commit
6298e5e5e2
  1. 11
      src/app/app.module.ts
  2. 226
      src/app/pages/internal-components/usertime.graph.component.ts
  3. 7
      src/app/pages/profile-page/profile-page.component.html
  4. 12
      src/app/pages/profile-page/profile-page.component.ts
  5. 15
      src/app/pages/servers-page/servers-page.component.html
  6. 25
      src/app/pages/servers-page/servers-page.component.ts
  7. 12
      src/app/services/graph.service.ts
  8. 45
      src/app/utils/BaseUtils.ts

11
src/app/app.module.ts

@ -83,6 +83,9 @@ import {AuthDialogRequest} from "./pages/internal-components/dialogs/AuthDialogR
import {SimpleActionDialog} from "./pages/internal-components/dialogs/simple-action-dialog.component";
import {MatTooltipModule} from "@angular/material/tooltip";
import {ServerPlayerViewer} from "./pages/servers-page/server-player-viewer";
import {MatTabsModule} from "@angular/material/tabs";
import {MatRadioModule} from "@angular/material/radio";
import {UsertimeGraphComponent} from "./pages/internal-components/usertime.graph.component";
registerLocaleData(localeRu, "ru")
@ -134,7 +137,9 @@ registerLocaleData(localeRu, "ru")
AdminMainPageComponent,
FilesPageComponent,
FilesUploader,
ServerPlayerViewer
ServerPlayerViewer,
//graph
UsertimeGraphComponent
],
imports: [
BrowserModule,
@ -167,7 +172,9 @@ registerLocaleData(localeRu, "ru")
MatDialogModule,
MatStepperModule,
MatCheckboxModule,
MatTooltipModule
MatTooltipModule,
MatTabsModule,
MatRadioModule
],
providers: [
{provide: LOCALE_ID, useValue: 'ru' },

226
src/app/pages/internal-components/usertime.graph.component.ts

@ -0,0 +1,226 @@
import {AfterViewInit, Component, Input, OnInit} from "@angular/core";
import {SearchFilter} from "../../entities/search/SearchFilter";
import {GraphService, PerPeriodStatistic} from "../../services/graph.service";
import {Chart} from "chart.js/auto";
import {MatSnackBar} from "@angular/material/snack-bar";
import {ServerService} from "../../services/server.service";
import {BaseUtils} from "../../utils/BaseUtils";
@Component({
selector: "app-usertime-graph",
template: `
<div>
<mat-radio-group
style="display: flex; justify-content: center;flex-direction: row;"
[(ngModel)]="usertimeChart.period"
[disabled]="usertimeChart.loading"
(ngModelChange)="updateUsertimeGraph()">
<mat-radio-button style="padding-right: 1%; padding-left: 1%" value="day">За месяц</mat-radio-button>
<mat-radio-button style="padding-right: 1%; padding-left: 1%" value="month">За год</mat-radio-button>
<mat-radio-button style="padding-right: 1%; padding-left: 1%" value="year">За 10 лет</mat-radio-button>
</mat-radio-group>
</div>
<div class="chart-container">
<canvas id="usertimeChart" >{{ usertimeChart.chart }}</canvas>
</div>`
})
export class UsertimeGraphComponent implements OnInit {
usertimeChart: {
chart: Chart|null
period: 'day' | 'month' | 'year',
loading: boolean
} = {
chart: null,
period: "day",
loading: false
};
@Input("steam64")
steam64: string|null = null;
serverList: {name: string, server_id: string }[] = [];
timeDelimiter: number = 1;
tooltipFooter = (items:any) => {
return "Наиграно: " + BaseUtils.formatSeconds(items.pop().parsed.y * this.timeDelimiter);
};
constructor(protected serverService: ServerService,
private graphService: GraphService,
private snack: MatSnackBar) {
}
ngOnInit(): void {
const fill = (res:any) => {
const keys = res.data ? Object.keys(res.data) : [];
for (const key of keys) {
// @ts-ignore
this.serverList.push({name: res.data[key].name, server_id: key});
}
}
this.serverService.getServers().subscribe(
(res) => {
fill(res);
}
)
}
getServerName(server_id: string|null) {
try {
// @ts-ignore
return this.serverList.filter(s => s.server_id == server_id).pop().name;
} catch (e) {
return "Неизвестно";
}
}
usertimeTabChanged(event: any) {
if (event.index == 1) {
this.updateUsertimeGraph()
}
}
updateUsertimeGraph() {
if (this.steam64 || true) {
if (this.usertimeChart.loading) return;
this.usertimeChart.loading = true;
const filter: SearchFilter = new SearchFilter();
if (this.steam64)
filter.addAccountToSearch(this.steam64);
let endDate = new Date();
endDate.setUTCHours(23, 59, 59);
filter.addEndTimeToSearch(endDate.getTime());
switch (this.usertimeChart.period) {
case "day": {
endDate.setDate(endDate.getDate() - 30);
break;
}
case "month": {
endDate.setDate(endDate.getDate() - 365);
break;
}
case "year": {
endDate.setDate(endDate.getDate() - (365*10));
break;
}
}
filter.addBeginTimeToSearch(endDate);
let chartConfig = {type: 'bar', data: {}, options: {
plugins: {
title: {
display: true,
text: "Наиграно секунд по серверам"
},
tooltip:{
callbacks: {
footer: this.tooltipFooter
}
}
},
responsive: true,
scales: {
x: {
stacked: true,
},
y: {
stacked: true
}
}
}
};
let chartData: {labels: string[], datasets: any[]} = {
labels: [],//даты
datasets: []
/**
* {
* label: 'Dataset 1',
* data: Utils.numbers(NUMBER_CFG),
* backgroundColor: Utils.CHART_COLORS.red,
* },
*/
};
let valueOnServer: {[srv_name: string]: number[]} = {}
let srvList: string[] = [];
let maxValue = 0;
this.graphService.getUsertimeOnPeriod(filter).subscribe(
(objs) => {
objs.forEach((obj) => {if (obj.value > maxValue) maxValue = obj.value})
if (maxValue >= 60) {
this.timeDelimiter = 60;
chartConfig.options.plugins.title.text = "Наиграно минут по серверам";
} //po minutam
if (maxValue >= 60 * 60) {
this.timeDelimiter = 60 * 60;
chartConfig.options.plugins.title.text = "Наиграно часов по серверам";
} //po chasam
if (maxValue >= 60 * 60 * 24) {
this.timeDelimiter = 60 * 60 * 24;
chartConfig.options.plugins.title.text = "Наиграно дней по серверам";
}//po dnyam
const groupByDate = objs.reduce((acc: {[date: string]: PerPeriodStatistic[]}, obj) => {
const key = obj.date;
if (!acc[key]) acc[key] = [];
acc[key].push(obj)
return acc;
}, {});
chartData.labels = Object.keys(groupByDate);
//console.log(groupByDate);
chartData.labels.forEach(
(date) => {
groupByDate[date].forEach((stat) => {
if (srvList.indexOf(stat.srv_id) == -1)
srvList.push(stat.srv_id)
})
}
);
//console.log(srvList)
srvList.forEach((srv) => {
valueOnServer[srv] = [];
});
chartData.labels.forEach(
(date) => {
let filled: string[] = [];
groupByDate[date].forEach(
(obj) => {
valueOnServer[obj.srv_id].push(obj.value / this.timeDelimiter)
filled.push(obj.srv_id);
}
)
srvList.forEach(
(srv) => {
if (filled.indexOf(srv) == -1) {
valueOnServer[srv].push(0);
}
}
)
},
)
//console.log(valueOnServer)
Object.keys(valueOnServer).forEach(
(key) => {
chartData.datasets.push(
{label: this.getServerName(key) + " ("+key+")", data: valueOnServer[key]}
)
}
)
///
chartConfig.data = chartData;
if (this.usertimeChart.chart)
this.usertimeChart.chart.destroy();
// @ts-ignore
this.usertimeChart.chart = new Chart('usertimeChart', chartConfig);
this.usertimeChart.loading = false;
}, (err) => {this.usertimeChart.loading = false; this.snack.open("Ошибка загрузка графика")}
);
} else {
this.snack.open("Нельзя загрузить график т.к нельзя определить индификатор профиля");
}
}
}

7
src/app/pages/profile-page/profile-page.component.html

@ -109,12 +109,19 @@
Наиграно времени
</mat-panel-title>
</mat-expansion-panel-header>
<mat-tab-group (selectedTabChange)="usertimegraph.usertimeTabChanged($event)">
<mat-tab label="По серверами">
<mat-list role="list">
<mat-list-item role="listitem" *ngFor="let gametime of profile.gametime | keyvalue">
<mat-progress-bar *ngIf="gametime.key == 'loading'" mode="indeterminate"></mat-progress-bar>
<p *ngIf="gametime.key != 'loading'">{{gametime.key}} - {{gametime.value | ValueServerMapDate:false}} секунд</p>
</mat-list-item>
</mat-list>
</mat-tab>
<mat-tab label="По датам">
<app-usertime-graph #usertimegraph [steam64]="steam64"></app-usertime-graph>
</mat-tab>
</mat-tab-group>
</mat-expansion-panel>
<!--История банов-->
<mat-expansion-panel hideToggle *ngIf="profile!=null && profile.steamids!=null" (click)="appbanlistsearchtable.lazyInit()">

12
src/app/pages/profile-page/profile-page.component.ts

@ -7,6 +7,11 @@ import {AuthService} from "../../services/auth.service";
import {ProfileRequestData} from "../../entities/profile/ProfileRequestData";
import {ActionService} from "../../services/action.service";
import {Ban} from "../../entities/ban/Ban";
import {Chart} from "chart.js/auto";
import {GraphService, PerPeriodStatistic} from "../../services/graph.service";
import {SearchFilter} from "../../entities/search/SearchFilter";
import _default from "chart.js/dist/plugins/plugin.legend";
import labels = _default.defaults.labels;
@Component({
selector: 'app-profile-page',
@ -17,6 +22,7 @@ export class ProfilePageComponent implements OnInit {
profile: PlayerProfile|null = null;
loading: boolean | null = null;
steam64: string|null = null;
constructor(private route: ActivatedRoute,
private playerService: PlayerService,
@ -25,9 +31,9 @@ export class ProfilePageComponent implements OnInit {
public actionService: ActionService) { }
ngOnInit(): void {
const steam64: string|null = this.route.snapshot.paramMap.get("steam64");
if (steam64 != null)
this.loadPlayer(steam64);
this.steam64 = this.route.snapshot.paramMap.get("steam64");
if (this.steam64 != null)
this.loadPlayer(this.steam64);
else {
}

15
src/app/pages/servers-page/servers-page.component.html

@ -111,7 +111,13 @@
<div class="content-in-center">
<div class="content-in-border">
<h2 style="color:#000;">График онлайна</h2>
<mat-tab-group (selectedTabChange)="onTabChanged($event)">
<mat-tab label="Инфо">
<div>
<p>Ты можешь оценить как живет сервер посмотрешь график онлайна или общие наигранное время на сервере</p>
</div>
</mat-tab>
<mat-tab label="График онлайна">
<div *ngIf="!init">
<div style="display: flex; justify-content: space-between">
<mat-form-field appearance="fill">
@ -153,6 +159,11 @@
<mat-progress-bar *ngIf="loading" mode="indeterminate"></mat-progress-bar>
<button mat-button mat-raised-button (click)="getGraph()" style="width: 100%">Обновить</button>
</div>
<button mat-button mat-raised-button *ngIf="init" (click)="getGraph()">Загрузить график</button>
</mat-tab>
<mat-tab label="Общие наигранное время">
<app-usertime-graph #usertimeGraphComponent></app-usertime-graph>
<mat-progress-bar *ngIf="usertimeGraphComponent.usertimeChart.loading" mode="indeterminate"></mat-progress-bar>
</mat-tab>
</mat-tab-group>
</div>
</div>

25
src/app/pages/servers-page/servers-page.component.ts

@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import {Component, OnInit, ViewChild} from '@angular/core';
import {WebsocketServersListenerService} from "../../services/websocket-servers-listener.service";
import {Server} from "../../entities/servers/Server";
import {KeyValue} from "@angular/common";
@ -13,6 +13,7 @@ import {Chart} from "chart.js/auto";
import {Period} from "../statistic-page/statistic-page.component";
import {GraphService} from "../../services/graph.service";
import {ServerService} from "../../services/server.service";
import {UsertimeGraphComponent} from "../internal-components/usertime.graph.component";
@Component({
selector: 'app-servers-page',
@ -32,6 +33,9 @@ export class ServersPageComponent implements OnInit {
init: boolean = true;
@ViewChild("usertimeGraphComponent")
usertimeGraphComponent!: UsertimeGraphComponent;
periods:Period[] = [
{name: 'По дням', value: 'days'},
{name: 'По минутам', value: 'minutes'}
@ -152,4 +156,23 @@ export class ServersPageComponent implements OnInit {
}
});
}
onTabChanged(event:any) {
switch (event.index) {
case 0: {
break;
}
case 1: {
this.getGraph();
break;
}
case 2: {
this.usertimeGraphComponent.updateUsertimeGraph();
break;
}
default: {
break;
}
}
}
}

12
src/app/services/graph.service.ts

@ -3,6 +3,13 @@ import {HttpClient} from "@angular/common/http";
import {map, Observable} from "rxjs";
import {StatsOfPeakOfDay} from "../entities/graph/StatsOfPeakOfDay";
import {StatsOfPeakOfPerFiveMinutes} from "../entities/graph/StatsOfPeakOfPerFiveMinutes";
import {SearchFilter} from "../entities/search/SearchFilter";
export interface PerPeriodStatistic {
value: number;
srv_id: string;
date: string;
}
@Injectable({
providedIn: 'root'
@ -18,4 +25,9 @@ export class GraphService {
public getOnlineStatsOfMinutes(minutes: number, limit: number, server_id: string):Observable<StatsOfPeakOfPerFiveMinutes[]> {
return this.http.get(`api/stats/graph/peak/of/minutes`, {params: {limit, server_id, minutes}}).pipe((map((res) => StatsOfPeakOfPerFiveMinutes.fromData(res))))
}
public getUsertimeOnPeriod(filter: SearchFilter): Observable<PerPeriodStatistic[]> {
// @ts-ignore
return this.http.post(`api/profile/usertime/graph`, filter);
}
}

45
src/app/utils/BaseUtils.ts

@ -2,4 +2,49 @@ export class BaseUtils {
openUrlInNewWindow(url: string) {
window.open(url, "_blank");
}
/** AI SLOOOP
* Преобразует секунды в формат "дни ЧЧ:ММ:СС" (если дни > 0)
* или просто "ЧЧ:ММ:СС" (если дней нет).
* @param {number} totalSeconds - количество секунд (целое неотрицательное число)
* @returns {string} отформатированная строка
*/
static formatSeconds(totalSeconds: number) {
if (totalSeconds < 0) return "0 00:00:00";
const days = Math.floor(totalSeconds / 86400);
let remainder = totalSeconds % 86400;
const hours = Math.floor(remainder / 3600);
remainder %= 3600;
const minutes = Math.floor(remainder / 60);
const seconds = remainder % 60;
// Форматируем часы, минуты, секунды с ведущими нулями
const timePart = `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
if (days === 0) {
return timePart.split(".")[0];;
}
// Склонение слова "день"
let dayWord;
const lastDigit = days % 10;
const lastTwoDigits = days % 100;
if (lastTwoDigits >= 11 && lastTwoDigits <= 14) {
dayWord = "дней";
} else {
switch (lastDigit) {
case 1: dayWord = "день"; break;
case 2:
case 3:
case 4: dayWord = "дня"; break;
default: dayWord = "дней";
}
}
return `${days} ${dayWord} ${timePart}`.split(".")[0];
}
}

Loading…
Cancel
Save