Network methods implementation

This commit is contained in:
everoddandeven 2024-10-07 22:02:58 +02:00
parent ca4745578e
commit 203fbc9968
9 changed files with 264 additions and 24 deletions

50
package-lock.json generated
View file

@ -19,7 +19,9 @@
"@angular/router": "17.3.6", "@angular/router": "17.3.6",
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3", "bootstrap-icons": "1.11.3",
"bootstrap-table": "1.23.4", "bootstrap-table": "1.23.5",
"chart.js": "4.4.4",
"chartjs": "0.3.24",
"crypto": "1.0.1", "crypto": "1.0.1",
"idb": "8.0.0", "idb": "8.0.0",
"jquery": "3.7.1", "jquery": "3.7.1",
@ -44,6 +46,7 @@
"@ngx-translate/http-loader": "8.0.0", "@ngx-translate/http-loader": "8.0.0",
"@playwright/test": "1.43.1", "@playwright/test": "1.43.1",
"@types/bootstrap": "5.2.10", "@types/bootstrap": "5.2.10",
"@types/chart.js": "2.9.41",
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/jquery": "3.5.30", "@types/jquery": "3.5.30",
"@types/node": "20.12.7", "@types/node": "20.12.7",
@ -4579,6 +4582,11 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"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",
@ -5807,6 +5815,15 @@
"@types/responselike": "^1.0.0" "@types/responselike": "^1.0.0"
} }
}, },
"node_modules/@types/chart.js": {
"version": "2.9.41",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.41.tgz",
"integrity": "sha512-3dvkDvueckY83UyUXtJMalYoH6faOLkWQoaTlJgB4Djde3oORmNP0Jw85HtzTuXyliUHcdp704s0mZFQKio/KQ==",
"dev": true,
"dependencies": {
"moment": "^2.10.2"
}
},
"node_modules/@types/connect": { "node_modules/@types/connect": {
"version": "3.4.38", "version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
@ -8278,9 +8295,9 @@
] ]
}, },
"node_modules/bootstrap-table": { "node_modules/bootstrap-table": {
"version": "1.23.4", "version": "1.23.5",
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.23.4.tgz", "resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.23.5.tgz",
"integrity": "sha512-3HHBJzzpg5z03F4eY8AHpLdW26FlzI0hFcmCb+8HRd5CbqLTd4ZJ5dfJUWFE+euU29rr6vkTyhSu44ucueYPDw==", "integrity": "sha512-9WByoSpJvA73gi2YYIlX6IWR74oZtBmSixul/Th8FTBtBd/kZRpbKESGTjhA3BA3AYTnfyY8Iy1KeRWPlV2GWQ==",
"peerDependencies": { "peerDependencies": {
"jquery": "3" "jquery": "3"
} }
@ -8988,6 +9005,22 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true "dev": true
}, },
"node_modules/chart.js": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.4.tgz",
"integrity": "sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/chartjs": {
"version": "0.3.24",
"resolved": "https://registry.npmjs.org/chartjs/-/chartjs-0.3.24.tgz",
"integrity": "sha512-h6G9qcDqmFYnSWqjWCzQMeOLiypS+pM6Fq2Rj7LPty8Kjx5yHonwwJ7oEHImZpQ2u9Pu36XGYfardvvBiQVrhg=="
},
"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",
@ -17982,6 +18015,15 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/mrmime": { "node_modules/mrmime": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",

View file

@ -56,7 +56,9 @@
"@angular/router": "17.3.6", "@angular/router": "17.3.6",
"bootstrap": "5.3.3", "bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3", "bootstrap-icons": "1.11.3",
"bootstrap-table": "1.23.4", "bootstrap-table": "1.23.5",
"chart.js": "4.4.4",
"chartjs": "0.3.24",
"crypto": "1.0.1", "crypto": "1.0.1",
"idb": "8.0.0", "idb": "8.0.0",
"jquery": "3.7.1", "jquery": "3.7.1",
@ -81,6 +83,7 @@
"@ngx-translate/http-loader": "8.0.0", "@ngx-translate/http-loader": "8.0.0",
"@playwright/test": "1.43.1", "@playwright/test": "1.43.1",
"@types/bootstrap": "5.2.10", "@types/bootstrap": "5.2.10",
"@types/chart.js": "2.9.41",
"@types/jest": "29.5.12", "@types/jest": "29.5.12",
"@types/jquery": "3.5.30", "@types/jquery": "3.5.30",
"@types/node": "20.12.7", "@types/node": "20.12.7",

View file

@ -1,6 +1,6 @@
import { EventEmitter, Injectable } from '@angular/core'; import { EventEmitter, Injectable } from '@angular/core';
import { DaemonService } from './daemon.service'; import { DaemonService } from './daemon.service';
import { BlockCount, BlockHeader, Chain, DaemonInfo, MinerData, MiningStatus, NetStats, SyncInfo } from '../../../../common'; import { BlockCount, BlockHeader, Chain, DaemonInfo, MinerData, MiningStatus, NetStats, NetStatsHistory, SyncInfo } from '../../../../common';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -35,6 +35,7 @@ export class DaemonDataService {
private _gettingAltChains: boolean = false; private _gettingAltChains: boolean = false;
private _netStats?: NetStats; private _netStats?: NetStats;
private _netStatsHistory: NetStatsHistory = new NetStatsHistory();
private _gettingNetStats: boolean = false; private _gettingNetStats: boolean = false;
private _miningStatus?: MiningStatus; private _miningStatus?: MiningStatus;
@ -50,6 +51,9 @@ export class DaemonDataService {
public readonly syncInfoRefreshStart: EventEmitter<void> = new EventEmitter<void>(); public readonly syncInfoRefreshStart: EventEmitter<void> = new EventEmitter<void>();
public readonly syncInfoRefreshEnd: EventEmitter<void> = new EventEmitter<void>(); public readonly syncInfoRefreshEnd: EventEmitter<void> = new EventEmitter<void>();
public readonly netStatsRefreshStart: EventEmitter<void> = new EventEmitter<void>();
public readonly netStatsRefreshEnd: EventEmitter<void> = new EventEmitter<void>();
constructor(private daemonService: DaemonService) { constructor(private daemonService: DaemonService) {
this.startLoop(); this.startLoop();
@ -139,6 +143,10 @@ export class DaemonDataService {
return this.netStats; return this.netStats;
} }
public get netStatsHistory(): NetStatsHistory {
return this._netStatsHistory;
}
public get gettingNetStats(): boolean { public get gettingNetStats(): boolean {
return this._gettingNetStats; return this._gettingNetStats;
} }
@ -271,7 +279,10 @@ export class DaemonDataService {
await this.refreshAltChains(); await this.refreshAltChains();
this._gettingNetStats = true; this._gettingNetStats = true;
this.netStatsRefreshStart.emit();
this._netStats = await this.daemonService.getNetStats(); this._netStats = await this.daemonService.getNetStats();
this._netStatsHistory.add(this._netStats);
this.netStatsRefreshEnd.emit();
this._gettingNetStats = false; this._gettingNetStats = false;
await this.refreshMiningStatus(); await this.refreshMiningStatus();

View file

@ -12,6 +12,13 @@
</div> </div>
<div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent"> <div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-net-stats" role="tabpanel" aria-labelledby="pills-net-stats-tab" tabindex="0">
<h2>Bytes In</h2>
<canvas class="my-4 w-100" id="netStatsBytesInChart" width="900" height="380"></canvas>
<h2>Bytes Out</h2>
<canvas class="my-4 w-100" id="netStatsBytesOutChart" width="900" height="380"></canvas>
</div>
</div> </div>
<app-daemon-not-running></app-daemon-not-running> <app-daemon-not-running></app-daemon-not-running>

View file

@ -1,7 +1,9 @@
import { AfterViewInit, Component } from '@angular/core'; import { AfterViewInit, Component } from '@angular/core';
import { NavbarService } from '../../shared/components/navbar/navbar.service'; import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { DaemonDataService } from '../../core/services'; import { DaemonDataService, DaemonService } from '../../core/services';
import { NavbarLink } from '../../shared/components/navbar/navbar.model'; import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { Chart, ChartData } from 'chart.js/auto'
import { NetStatsHistoryEntry } from '../../../common';
@Component({ @Component({
selector: 'app-network', selector: 'app-network',
@ -9,22 +11,149 @@ import { NavbarLink } from '../../shared/components/navbar/navbar.model';
styleUrl: './network.component.scss' styleUrl: './network.component.scss'
}) })
export class NetworkComponent implements AfterViewInit { export class NetworkComponent implements AfterViewInit {
public daemonRunning: boolean = false;
public readonly navbarLinks: NavbarLink[]; public readonly navbarLinks: NavbarLink[];
constructor(private navbarService: NavbarService, private daemonData: DaemonDataService) { private netStatsBytesInChart?: Chart;
private netStatsBytesOutChart?: Chart;
public get daemonRunning(): boolean {
return this.daemonData.running;
}
constructor(private navbarService: NavbarService, private daemonService: DaemonService, private daemonData: DaemonDataService) {
this.navbarLinks = [ this.navbarLinks = [
new NavbarLink('pills-net-stats-tab', '#pills-net-stats', 'pills-net-stats', false, 'Statistics'), new NavbarLink('pills-net-stats-tab', '#pills-net-stats', 'pills-net-stats', false, 'Statistics'),
new NavbarLink('pills-limits-tab', '#pills-limits', 'pills-limits', false, 'Limits'), new NavbarLink('pills-limits-tab', '#pills-limits', 'pills-limits', false, 'Limits'),
new NavbarLink('pills-public-nodes-tab', '#pills-public-nodes', 'pills-public-nodes', false, 'Public Nodes') new NavbarLink('pills-public-nodes-tab', '#pills-public-nodes', 'pills-public-nodes', false, 'Public Nodes')
]; ];
this.daemonData.netStatsRefreshEnd.subscribe(() => {
this.refreshNetStatsHistory();
});
this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => {
if (!running) {
if (this.netStatsBytesInChart) {
this.netStatsBytesInChart.destroy();
this.netStatsBytesInChart = undefined;
}
}
});
} }
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
this.daemonRunning = this.daemonData.running;
this.navbarService.setLinks(this.navbarLinks); this.navbarService.setLinks(this.navbarLinks);
this.initNetStatsHistoryChart();
} }
private buildChartBytesInData(): ChartData {
const labels: string [] = [];
const data: number[] = [];
this.daemonData.netStatsHistory.history.forEach((entry: NetStatsHistoryEntry) => {
labels.push(`${entry.date.toLocaleTimeString()} ${entry.date.toLocaleDateString()}`);
data.push(entry.netStats.totalBytesIn);
});
return {
labels: labels,
datasets: [{
data: data,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
};
}
private buildChartBytesOutData(): ChartData {
const labels: string [] = [];
const data: number[] = [];
this.daemonData.netStatsHistory.history.forEach((entry: NetStatsHistoryEntry) => {
labels.push(`${entry.date.toLocaleTimeString()} ${entry.date.toLocaleDateString()}`);
data.push(entry.netStats.totalBytesOut);
});
return {
labels: labels,
datasets: [{
data: data,
backgroundColor: 'transparent',
borderColor: '#007bff',
borderWidth: 4,
pointBackgroundColor: '#007bff'
}]
};
}
private initNetStatsHistoryChart(): void {
const ctx1 = <HTMLCanvasElement>document.getElementById('netStatsBytesInChart');
const ctx2 = <HTMLCanvasElement>document.getElementById('netStatsBytesOutChart');
if (!ctx1 || !ctx2) {
return;
}
this.netStatsBytesInChart = new Chart(ctx1, {
type: 'line',
data: this.buildChartBytesInData(),
options: {
plugins: {
legend: {
display: false
},
tooltip: {
boxPadding: 3
}
}
}
});
this.netStatsBytesOutChart = new Chart(ctx2, {
type: 'line',
data: this.buildChartBytesOutData(),
options: {
plugins: {
legend: {
display: false
},
tooltip: {
boxPadding: 3
}
}
}
});
this.netStatsBytesInChart.update();
this.netStatsBytesOutChart.update();
}
private refreshNetStatsHistory(): void {
if (!this.netStatsBytesInChart) {
return;
}
const last = this.daemonData.netStatsHistory.last;
if (!this.netStatsBytesInChart || !this.netStatsBytesOutChart) {
this.initNetStatsHistoryChart();
}
else if (last) {
const label = `${last.date.toLocaleTimeString()} ${last.date.toLocaleDateString()}`;
this.netStatsBytesInChart.data.labels?.push(label);
this.netStatsBytesInChart.data.datasets.forEach((dataset) => {
dataset.data.push(last.netStats.totalBytesIn);
});
this.netStatsBytesOutChart.data.labels?.push(label);
this.netStatsBytesOutChart.data.datasets.forEach((dataset) => {
dataset.data.push(last.netStats.totalBytesOut);
});
this.netStatsBytesInChart.update();
this.netStatsBytesOutChart.update();
}
}
} }

View file

@ -21,23 +21,19 @@
<h4 class="mb-3">Node</h4> <h4 class="mb-3">Node</h4>
<div class="row gy-3"> <div class="row gy-3">
<div class="col-md-12"> <div class="col-md-12">
<label for="general-monerod-path" class="form-label">Monerod path</label> <label for="general-monerod-path-control" class="form-label">Monerod path</label>
<input type="file" class="form-control" id="general-monerod-path" [(ngModel)]="currentSettings.monerodPath" [ngModelOptions]="{standalone: true}" (change)="onMonerodPathChange()" placeholder="AAA"> <div class="input-group mb-3">
<input id="general-monerod-path-control" type="text" class="form-control form-control-sm" placeholder="" aria-label="Monerod path" aria-describedby="basic-addon2" [value]="currentSettings.monerodPath" readonly>
<span class="input-group-text" id="basic-addon2"><button type="button" class="btn btn-secondary btn-sm" (click)="chooseMonerodFile()">Choose file</button></span>
</div>
<input type="file" class="form-control d-none" id="general-monerod-path" [(ngModel)]="currentSettings.monerodPath" [ngModelOptions]="{standalone: true}" (change)="onMonerodPathChange()" placeholder="AAA">
<small class="text-body-secondary">Path to monerod executable</small> <small class="text-body-secondary">Path to monerod executable</small>
</div> </div>
</div>
<br>
<div class="row gy-3">
<div class="col-md-12">
<label for="general-xmrig-path" class="form-label">XMRig path</label>
<input type="file" class="form-control" id="general-xmrig-path" [(ngModel)]="currentSettings.monerodPath" [ngModelOptions]="{standalone: true}" (change)="onMonerodPathChange()" placeholder="AAA">
<small class="text-body-secondary">Path to XMRig executable</small>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -147,4 +147,24 @@ export class SettingsComponent implements AfterViewInit {
this.originalSettings = this.currentSettings.clone(); this.originalSettings = this.currentSettings.clone();
} }
public chooseMonerodFile(): void {
const input = document.getElementById('general-monerod-path');
if (!input) {
return;
}
input.click();
}
public chooseXmrigFile(): void {
const input = document.getElementById('general-xmrig-path');
if (!input) {
return;
}
input.click();
}
} }

View file

@ -0,0 +1,31 @@
import { NetStats } from "./NetStats";
export type NetStatsHistoryEntry = { date: Date, netStats: NetStats};
export class NetStatsHistory {
private _history: NetStatsHistoryEntry[];
private _last?: NetStatsHistoryEntry;
public get history(): NetStatsHistoryEntry[] {
return this._history;
}
public get last(): NetStatsHistoryEntry | undefined {
return this._last;
}
constructor() {
this._history = [];
}
public add(netStats: NetStats): void {
const entry = {
date: new Date(),
netStats: netStats
};
this._history.push(entry);
this._last = entry;
}
}

View file

@ -33,6 +33,7 @@ export { TxBacklogEntry } from './TxBacklogEntry';
export { TxInfo } from './TxInfo'; export { TxInfo } from './TxInfo';
export { UpdateInfo } from './UpdateInfo'; export { UpdateInfo } from './UpdateInfo';
export { LogCategoryLevel, LogCategories } from './LogCategories'; export { LogCategoryLevel, LogCategories } from './LogCategories';
export { NetStatsHistory, NetStatsHistoryEntry } from './NetStatsHistory';
export * from './error'; export * from './error';
export * from './request'; export * from './request';