Browse Source

stats extra

master
gsd 1 month ago
parent
commit
3be466a559
  1. 19
      package-lock.json
  2. 1
      package.json
  3. 6
      src/app/app.component.html
  4. 25
      src/app/entities/graph/GraphData.ts
  5. 2
      src/app/entities/graph/StatsOfPeakOfDay.ts
  6. 2
      src/app/entities/graph/StatsOfPeakOfPerFiveMinutes.ts
  7. 1
      src/app/entities/profile/ProfileRequestData.ts
  8. 2
      src/app/pages/internal-components/abstract-search-table.component.ts
  9. 4
      src/app/pages/profile-page/profile-page.component.html
  10. 49
      src/app/pages/statistic-page/statistic-page.component.html
  11. 49
      src/app/pages/statistic-page/statistic-page.component.ts
  12. 29
      src/app/services/auth.service.ts
  13. 14
      src/app/services/server.service.ts

19
package-lock.json

@ -19,6 +19,7 @@
"@angular/platform-browser-dynamic": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0", "@angular/router": "^14.2.0",
"@types/leaflet": "^1.9.17", "@types/leaflet": "^1.9.17",
"chart.js": "^4.4.8",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
@ -2975,6 +2976,12 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
"license": "MIT"
},
"node_modules/@leichtgewicht/ip-codec": { "node_modules/@leichtgewicht/ip-codec": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz",
@ -4500,6 +4507,18 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/chart.js": {
"version": "4.4.8",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.8.tgz",
"integrity": "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA==",
"license": "MIT",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",

1
package.json

@ -21,6 +21,7 @@
"@angular/platform-browser-dynamic": "^14.2.0", "@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0", "@angular/router": "^14.2.0",
"@types/leaflet": "^1.9.17", "@types/leaflet": "^1.9.17",
"chart.js": "^4.4.8",
"leaflet": "^1.9.4", "leaflet": "^1.9.4",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",

6
src/app/app.component.html

@ -16,9 +16,9 @@
{{link.name}} {{link.name}}
</button> </button>
<button *ngIf="auth_service.steamdata?.nickname == null" mat-button mat-raised-button class="f13_color_primary" (click)="login()">Войти</button> <button *ngIf="auth_service.steamIds == null" mat-button mat-raised-button class="f13_color_primary" (click)="login()">Войти</button>
<button *ngIf="auth_service.steamdata?.nickname != null" mat-button mat-raised-button class="f13_color_primary" [matMenuTriggerFor]="menu"> <button *ngIf="auth_service.steamIds != null" mat-button mat-raised-button class="f13_color_primary" [matMenuTriggerFor]="menu">
{{auth_service.steamdata?.nickname}} {{auth_service.steamdata?.nickname != null ? auth_service.steamdata?.nickname:'Мой профиль' }}
</button> </button>
<mat-menu #discordMenu> <mat-menu #discordMenu>
<button mat-menu-item (click)="openDiscordConnect()">Привязать аккаунт</button> <button mat-menu-item (click)="openDiscordConnect()">Привязать аккаунт</button>

25
src/app/entities/graph/GraphData.ts

@ -0,0 +1,25 @@
import {StatsOfPeakOfDay} from "./StatsOfPeakOfDay";
import {StatsOfPeakOfPerFiveMinutes} from "./StatsOfPeakOfPerFiveMinutes";
export class GraphData {
labels: any[] = [];
data: {data:any[], label: string, backgroundColor: string}[] = [];
static fromStatsOfPeakOfDay(d: StatsOfPeakOfDay[], color: string) {
const g = new GraphData();
g.labels = d.map((gg) => gg.date);
g.data.push(
{label: "Игроков", data: d.map((gg) => gg.avg), backgroundColor: color}
)
return g;
}
static fromStatsOfPeakOfPerFiveMinutes(d: StatsOfPeakOfPerFiveMinutes[], color: string) {
const g = new GraphData();
g.labels = d.map((gg) => gg.timestamp.toLocaleString());
g.data.push(
{label: "Игроков", data: d.map((gg) => gg.max), backgroundColor: color}
)
return g;
}
}

2
src/app/entities/graph/StatsOfPeakOfDay.ts

@ -1,3 +1,5 @@
import {GraphData} from "./GraphData";
export class StatsOfPeakOfDay { export class StatsOfPeakOfDay {
date!: string; date!: string;
max!:number; max!:number;

2
src/app/entities/graph/StatsOfPeakOfPerFiveMinutes.ts

@ -1,3 +1,5 @@
import {GraphData} from "./GraphData";
export class StatsOfPeakOfPerFiveMinutes { export class StatsOfPeakOfPerFiveMinutes {
timestamp!:Date; timestamp!:Date;
max!:number; max!:number;

1
src/app/entities/profile/ProfileRequestData.ts

@ -11,6 +11,7 @@ export class ProfileRequestData {
static KILLFEED: ProfileRequestData = new ProfileRequestData("killfeed", "Информация о убийствах, смертях, помощи"); static KILLFEED: ProfileRequestData = new ProfileRequestData("killfeed", "Информация о убийствах, смертях, помощи");
static REPORTS: ProfileRequestData = new ProfileRequestData("reports", "Информация о количестве репортах"); static REPORTS: ProfileRequestData = new ProfileRequestData("reports", "Информация о количестве репортах");
static MESSAGES: ProfileRequestData = new ProfileRequestData("messages", "Информация о количестве сообщений"); static MESSAGES: ProfileRequestData = new ProfileRequestData("messages", "Информация о количестве сообщений");
static POHUY: ProfileRequestData = new ProfileRequestData("pohuy", "Проверка что авторизован")
static BASIC_PROFILE: ProfileRequestData[] = [ static BASIC_PROFILE: ProfileRequestData[] = [
ProfileRequestData.PLAY_ON, ProfileRequestData.PLAY_ON,

2
src/app/pages/internal-components/abstract-search-table.component.ts

@ -52,7 +52,7 @@ export abstract class AbstractSearchTable<T,U extends SearchFilter> implements A
this.filter.addAccountToSearch(`[U:1:${this.account_id}]`); this.filter.addAccountToSearch(`[U:1:${this.account_id}]`);
this.updateData(); this.updateData();
this.serverService.getStats("servers").subscribe( this.serverService.servers.subscribe(
(res) => { (res) => {
const keys = Object.keys(res.data); const keys = Object.keys(res.data);
for (const key of keys) { for (const key of keys) {

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

@ -1,5 +1,5 @@
<div class="content-in-center-header" style="flex-direction: column;"> <div class="content-in-center-header" style="flex-direction: column;">
<div *ngIf="authService.steamdata == null && loading == null"> <div *ngIf="!authService.isAuth() && loading == null">
<h1>Профиль</h1> <h1>Профиль</h1>
<h3>Здесь можно увидеть профиль игрока на наших серверах с подробной информации о нем</h3> <h3>Здесь можно увидеть профиль игрока на наших серверах с подробной информации о нем</h3>
</div> </div>
@ -15,7 +15,7 @@
<div class="content-in-center"> <div class="content-in-center">
<div class="content-in-border"> <div class="content-in-border">
<app-need-auth-to-continue *ngIf="authService.steamdata == null && loading == null"></app-need-auth-to-continue> <app-need-auth-to-continue *ngIf="!authService.isAuth() && loading == null"></app-need-auth-to-continue>
<div *ngIf="loading == false && profile != null"> <div *ngIf="loading == false && profile != null">
<div class="container responsive-grid-400"> <div class="container responsive-grid-400">
<mat-card class="example-card"> <mat-card class="example-card">

49
src/app/pages/statistic-page/statistic-page.component.html

@ -1,15 +1,60 @@
<div class="content-in-center-header" style="flex-direction: column;"> <div class="content-in-center-header" style="flex-direction: column;">
<h1>Статистика</h1> <h1>Статистика</h1>
<h3>Скоро и ты сюда попадешь браток</h3> <h3>Разнообразная статистика с наших серверов</h3>
</div> </div>
<div class="content-in-center"> <div class="content-in-center">
<div class="content-in-border"> <div class="content-in-border">
<h2>График онлайна</h2> <h2>График онлайна</h2>
<div *ngIf="!init">
<div style="display: flex; justify-content: space-between">
<mat-form-field appearance="fill">
<mat-label>Статистика по</mat-label>
<mat-select [(ngModel)]="selectedPeriod">
<mat-option *ngFor="let s of periods" [value]="s">
{{s.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill" *ngIf="selectedPeriod.value=='minutes'">
<mat-label>Количество минут</mat-label>
<mat-select [(ngModel)]="selectedMinute">
<mat-option *ngFor="let s of minutes" [value]="s">
Разница в {{s}} минут
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Количество дней</mat-label>
<mat-select [(ngModel)]="selectedDays">
<mat-option *ngFor="let s of days" [value]="s">
За {{s}} дней
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Сервер</mat-label>
<mat-select [(ngModel)]="selectedServer">
<mat-option *ngFor="let s of serverList" [value]="s">
{{s.name}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="chart-container">
<canvas id="MyChart" >{{ chart }}</canvas>
</div>
<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>
</div>
<div class="content-in-border">
<h2>Уникальные игроки</h2>
<div></div> <div></div>
</div> </div>
<div class="content-in-border"> <div class="content-in-border">
<h2>Страны</h2> <h2>Последние выданые випки</h2>
<div></div> <div></div>
</div> </div>
</div> </div>

49
src/app/pages/statistic-page/statistic-page.component.ts

@ -1,5 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import {GraphService} from "../../services/graph.service"; import {GraphService} from "../../services/graph.service";
import {Chart} from "chart.js/auto";
import {GraphData} from "../../entities/graph/GraphData";
import {ActionService} from "../../services/action.service";
export interface Period { export interface Period {
name: string name: string
@ -25,14 +28,58 @@ export class StatisticPageComponent implements OnInit {
days:number[] = [1,7,14,21,30,60,90,180]; days:number[] = [1,7,14,21,30,60,90,180];
selectedDays:number = 30; selectedDays:number = 30;
constructor(private graphService: GraphService) { } minutes:number[] = [1, 5, 10, 15, 30]
selectedMinute:number = 5;
serverList: {name: string, server_id: string }[] = [
{name: 'Выбрать все', server_id: '%'}
];
selectedServer: {name: string, server_id: string } = this.serverList[0];
chart!:Chart;
constructor(private graphService: GraphService,
private actionService: ActionService) { }
ngOnInit(): void { ngOnInit(): void {
} }
getGraph() { getGraph() {
if (this.loading) {
return;
}
this.init = false; this.init = false;
this.loading = true; this.loading = true;
console.log(this.selectedPeriod, this.selectedMinute, this.selectedDays, this.selectedServer);
switch (this.selectedPeriod.value) {
case 'days': {
this.graphService.getOnlineStatsOfDays(this.selectedDays, this.selectedServer?this.selectedServer.server_id:'%').subscribe(
(d) => this.processingData(GraphData.fromStatsOfPeakOfDay(d, "red")),
(err) => this.actionService.showSnack('Произошла ошибка во время загрузки данных, попробуй позже'),
() => this.loading = false
); break;
}
case 'minutes': {
this.graphService.getOnlineStatsOfMinutes(this.selectedMinute, this.selectedDays, this.selectedServer?this.selectedServer.server_id:'%').subscribe(
(d) => this.processingData(GraphData.fromStatsOfPeakOfPerFiveMinutes(d, "red")),
(err) => this.actionService.showSnack('Произошла ошибка во время загрузки данных, попробуй позже'),
() => this.loading = false
); break;
}
}
} }
processingData(data:GraphData) {
if (this.chart)
this.chart.destroy();
this.chart = new Chart("MyChart", {
type: "line",
data: {
labels: data.labels,
datasets: data.data
}
});
}
} }

29
src/app/services/auth.service.ts

@ -9,27 +9,38 @@ import {map, Observable} from "rxjs";
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {DiscordAccount} from "../entities/DiscordAccount"; import {DiscordAccount} from "../entities/DiscordAccount";
export interface StorageUser {steamdata: SteamData|null, steamIds: SteamIDs|null, permition: Permition|null}
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthService { export class AuthService {
static KEY: string = "steam_ids"; static KEY: string = "user";
steamdata: SteamData | null = null; steamdata: SteamData | null = null;
steamIds: SteamIDs | null = null; steamIds: SteamIDs | null = null;
permition: Permition | null = null; permition: Permition | null = null;
constructor(private playerService: PlayerService, private router: Router, private http: HttpClient) { constructor(private playerService: PlayerService, private router: Router, private http: HttpClient) {
this.playerService.getProfile(null, [ProfileRequestData.POHUY]).subscribe(
() => {
if (sessionStorage.getItem(AuthService.KEY) == null) {
this.playerService.getProfile(null, [ProfileRequestData.STEAM_DATA, ProfileRequestData.PERMITION]) this.playerService.getProfile(null, [ProfileRequestData.STEAM_DATA, ProfileRequestData.PERMITION])
.subscribe((res) => { .subscribe((res) => {
this.steamdata = res.steam_data; const user: StorageUser = {
this.steamIds = res.steamids steamdata: res.steam_data, steamIds: res.steamids, permition: res.permition
this.permition = res.permition; }
sessionStorage.setItem(AuthService.KEY, JSON.stringify(res.steamids)) sessionStorage.setItem(AuthService.KEY, JSON.stringify(user))
}, (err) => {
if (err.status == 401)
sessionStorage.removeItem(AuthService.KEY);
console.log(err);
}) })
} else {
// @ts-ignore
const user: StorageUser = JSON.parse(sessionStorage.getItem(AuthService.KEY));
this.steamdata = user.steamdata;
this.steamIds = user.steamIds;
this.permition = user.permition;
console.log("user data restored", user);
}
}, (err) => {err.status==401?sessionStorage.removeItem(AuthService.KEY):null}
)
} }
login() { login() {

14
src/app/services/server.service.ts

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import {HttpClient} from "@angular/common/http"; import {HttpClient} from "@angular/common/http";
import {map, Observable} from "rxjs"; import {map, Observable, of} from "rxjs";
import {StatExporter} from "../entities/servers/StatExporter"; import {StatExporter} from "../entities/servers/StatExporter";
@Injectable({ @Injectable({
@ -8,7 +8,17 @@ import {StatExporter} from "../entities/servers/StatExporter";
}) })
export class ServerService { export class ServerService {
constructor(private http: HttpClient) { } constructor(private http: HttpClient) {
this.http.get(`api/stats?filter=servers`).pipe(
map((res) => {
const d = new StatExporter();
d.fromData(res, 'servers');
return d;
})).subscribe((res) => {
this.servers = of(res);
})
}
servers: Observable<StatExporter<any>> = of();
getStats(filter: string): Observable<StatExporter<any>> { getStats(filter: string): Observable<StatExporter<any>> {
return this.http.get(`api/stats?filter=${filter}`).pipe( return this.http.get(`api/stats?filter=${filter}`).pipe(

Loading…
Cancel
Save