Logs methods implementation, transactions methods implementation, network component implementation

This commit is contained in:
everoddandeven 2024-10-04 19:05:10 +02:00
parent 91b38d1fb4
commit e6cb2abcb0
18 changed files with 482 additions and 24 deletions

View file

@ -27,6 +27,7 @@ import { SettingsModule } from './pages/settings/settings.module';
import { LogsModule } from './pages/logs/logs.module';
import { VersionModule } from './pages/version/version.module';
import { HardForkInfoModule } from './pages/hard-fork-info/hard-fork-info.module';
import { NetworkModule } from './pages/network/network.module';
// AoT requires an exported function for factories
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
@ -50,6 +51,7 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
SettingsModule,
HardForkInfoModule,
VersionModule,
NetworkModule,
TranslateModule,
AppRoutingModule,
TranslateModule.forRoot({

View file

@ -1,6 +1,6 @@
import { EventEmitter, Injectable } from '@angular/core';
import { DaemonService } from './daemon.service';
import { BlockCount, BlockHeader, Chain, DaemonInfo, SyncInfo } from '../../../../common';
import { BlockCount, BlockHeader, Chain, DaemonInfo, NetStats, SyncInfo } from '../../../../common';
@Injectable({
providedIn: 'root'
@ -34,6 +34,9 @@ export class DaemonDataService {
private _altChains: Chain[] = [];
private _gettingAltChains: boolean = false;
private _netStats?: NetStats;
private _gettingNetStats: boolean = false;
public readonly syncStart: EventEmitter<void> = new EventEmitter<void>();
public readonly syncEnd: EventEmitter<void> = new EventEmitter<void>();
public readonly syncError: EventEmitter<Error> = new EventEmitter<Error>();
@ -126,6 +129,14 @@ export class DaemonDataService {
return this._gettingAltChains;
}
public get netStats(): NetStats | undefined {
return this.netStats;
}
public get gettingNetStats(): boolean {
return this._gettingNetStats;
}
public setRefreshTimeout(ms: number = 5000): void {
this.refreshTimeoutMs = ms;
}
@ -203,6 +214,10 @@ export class DaemonDataService {
this._altChains = await this.daemonService.getAlternateChains();
this._gettingAltChains = false;
this._gettingNetStats = true;
this._netStats = await this.daemonService.getNetStats();
this._gettingNetStats = false;
this._lastRefresh = Date.now();
} catch(error) {
console.error(error);
@ -212,6 +227,7 @@ export class DaemonDataService {
this._gettingLastBlockHeader = false;
this._gettingIsBlockchainPruned = false;
this._gettingAltChains = false;
this._gettingNetStats = false;
this.syncError.emit(<Error>error);

View file

@ -38,7 +38,10 @@ import {
IsKeyImageSpentRequest,
GetAltBlockHashesRequest,
SaveBcRequest,
SetBootstrapDaemonRequest
SetBootstrapDaemonRequest,
SetLogLevelRequest,
SetLogHashRateRequest,
SetLogCategoriesRequest
} from '../../../../common/request';
import { BlockTemplate } from '../../../../common/BlockTemplate';
import { GeneratedBlocks } from '../../../../common/GeneratedBlocks';
@ -705,6 +708,10 @@ export class DaemonService {
public async sendRawTransaction(txAsHex: string, doNotRelay: boolean = false): Promise<TxInfo> {
const response = await this.callRpc(new SendRawTransactionRequest(txAsHex, doNotRelay));
if (typeof response.status == 'string' && response.status != 'OK') {
throw new Error(response.reason);
}
return TxInfo.parse(response);
}
@ -836,6 +843,30 @@ export class DaemonService {
return await this.update('download', path);
}
public async setLogLevel(level: number): Promise<void> {
const response = await this.callRpc(new SetLogLevelRequest(level));
if (response.status != 'OK') {
throw new Error(response.status);
}
}
public async setLogCategories(cateogories: string): Promise<void> {
const response = await this.callRpc(new SetLogCategoriesRequest(cateogories));
if (response.status != 'OK') {
throw new Error(response.status);
}
}
public async setLogHashRate(visible: boolean): Promise<void> {
const response = await this.callRpc(new SetLogHashRateRequest(visible));
if (response.status != 'OK') {
throw new Error(response.status);
}
}
public getGuiVersion(): string {
return "0.1.0-alpha";
}

View file

@ -2,12 +2,17 @@
<h1 class="h2">Logs</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<ul class="nav nav-pills m-3" id="pills-tab" role="tablist">
@for(navbarLink of navbarLinks; track navbarLink.name) {
<li class="nav-item mr-2" role="presentation">
<button [class]="true ? 'nav-link active btn-sm' : 'nav-link btn-sm'" id="" data-bs-toggle="pill" [attr.data-bs-target]="" type="button" role="tab" [attr.aria-controls]="" [attr.aria-selected]="true" [disabled]="false">Overview</button>
<button [class]="navbarLink.selected ? 'nav-link active btn-sm' : 'nav-link btn-sm'" [id]="navbarLink.id" data-bs-toggle="pill" [attr.data-bs-target]="navbarLink.target" type="button" role="tab" [attr.aria-controls]="navbarLink.controls" [attr.aria-selected]="navbarLink.selected" [disabled]="navbarLink.disabled">{{navbarLink.name}}</button>
</li>
}
</ul>
</div>
</div>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-overview" role="tabpanel" aria-labelledby="pills-overview-tab" tabindex="0">
<div *ngIf="lines.length == 0" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
<h2><i class="bi bi-exclamation-diamond m-4"></i> No logs</h2>
<p>Start monero daemon to enable session logging</p>
@ -20,4 +25,87 @@
</ng-container>
</div>
</div>
</div>
<div class="tab-pane fade" id="pills-set-log-level" role="tabpanel" aria-labelledby="pills-set-log-level-tab" tabindex="0">
<div *ngIf="setLogLevelError != ''" class="alert alert-danger d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-exclamation-triangle m-2"></i></h4>&nbsp;&nbsp;
<div>
{{setLogLevelError}}
</div>
</div>
<div *ngIf="setLogLevelSuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-check-circle m-2"></i></h4>&nbsp;&nbsp;
<div>
Successfully set log level
</div>
</div>
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<h4 class="mb-3">Set the daemon log level</h4>
<div class="col-md-4">
<label for="set-log-level-level" class="form-label">Log Level</label>
<select class="form-select" id="set-log-level-level" [(ngModel)]="setLogLevelLevel" [ngModelOptions]="{standalone: true}">
<option [ngValue]="0" [selected]="setLogLevelLevel == 0">0</option>
<option [ngValue]="1" [selected]="setLogLevelLevel == 1">1</option>
<option [ngValue]="2" [selected]="setLogLevelLevel == 2">2</option>
<option [ngValue]="3" [selected]="setLogLevelLevel == 3">3</option>
<option [ngValue]="4" [selected]="setLogLevelLevel == 4">4</option>
</select>
</div>
</div>
</div>
<hr class="my-4">
<button *ngIf="!settingLogLevel" class="w-100 btn btn-primary btn-lg" type="button" (click)="setLogLevel()">Set Log Level</button>
<button *ngIf="settingLogLevel" class="w-100 btn btn-primary btn-lg" type="button" disabled>Setting log level ...</button>
</div>
<div class="tab-pane fade" id="pills-set-log-categories" role="tabpanel" aria-labelledby="pills-set-log-categories-tab" tabindex="0">
</div>
<div class="tab-pane fade" id="pills-set-log-hash-rate" role="tabpanel" aria-labelledby="pills-set-log-hash-rate-tab" tabindex="0">
<div *ngIf="setLogHashRateError != ''" class="alert alert-danger d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-exclamation-triangle m-2"></i></h4>&nbsp;&nbsp;
<div>
{{setLogHashRateError}}
</div>
</div>
<div *ngIf="setLogHashRateSuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-check-circle m-2"></i></h4>&nbsp;&nbsp;
<div>
Successfully set log hash rate
</div>
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<h4 class="mb-3">Set the log hash rate display mode</h4>
<div class="row gy-3">
<div class="form-check form-switch col-md-6">
<label for="set-log-hash-rate-enabled" class="form-check-label">Enabled</label>
<input class="form-control form-check-input" type="checkbox" role="switch" id="set-log-hash-rate-enabled" [checked]="setLogHashRateEnabled" [(ngModel)]="setLogHashRateEnabled" [ngModelOptions]="{standalone: true}">
<br>
<small class="text-body-secondary">States if hash rate logs should be visible or hidden</small>
</div>
</div>
</div>
</div>
<hr class="my-4">
<button *ngIf="!settingLogHashRate" class="w-100 btn btn-primary btn-lg" type="button" (click)="setLogHashRate()">Set Log Hash Rate</button>
<button *ngIf="settingLogHashRate" class="w-100 btn btn-primary btn-lg" type="button" disabled>Setting Log Hash Rate ...</button>
</div>
</div>

View file

@ -1,6 +1,8 @@
import { AfterViewInit, Component, ElementRef, NgZone, ViewChild } from '@angular/core';
import { LogsService } from './logs.service';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonService } from '../../core/services';
@Component({
selector: 'app-logs',
@ -9,9 +11,31 @@ import { NavbarService } from '../../shared/components/navbar/navbar.service';
})
export class LogsComponent implements AfterViewInit {
@ViewChild('logTerminal', { read: ElementRef }) public logTerminal?: ElementRef<any>;
public readonly navbarLinks: NavbarLink[];
constructor(private navbarService: NavbarService, private logsService: LogsService, private ngZone: NgZone) {
public setLogLevelLevel: number = 0;
public settingLogLevel: boolean = false;
public setLogLevelError: string = '';
public setLogLevelSuccess: boolean = false;
public setLogCategoriesCategories: string = '';
public settingLogCategories: boolean = false;
public setLogCategoriesError: string = '';
public setLogCategoriesSuccess: boolean = false;
public setLogHashRateEnabled: boolean = false;
public settingLogHashRate: boolean = false;
public setLogHashRateError: string = '';
public setLogHashRateSuccess: boolean = false;
constructor(private navbarService: NavbarService, private logsService: LogsService, private daemonService: DaemonService, private ngZone: NgZone) {
this.logsService.onLog.subscribe((message: string) => this.onLog());
this.navbarLinks = [
new NavbarLink('pills-overview-tab', '#pills-overview', 'pills-overview', true, 'Overview'),
new NavbarLink('pills-set-log-level-tab', '#pills-set-log-level', 'pills-set-log-level', false, 'Set Log Level'),
new NavbarLink('pills-set-log-categories-tab', '#pills-set-log-categories', 'pills-set-log-categories', false, 'Set Log Categories'),
new NavbarLink('pills-set-log-hash-rate-tab', '#pills-set-log-hash-rate', 'pills-set-log-hash-rate', false, 'Set Log Hash Rate')
];
}
public get lines(): string[] {
@ -42,10 +66,45 @@ export class LogsComponent implements AfterViewInit {
return index; // usa l'indice per tracciare gli elementi
}
ngAfterViewInit(): void {
public ngAfterViewInit(): void {
this.navbarService.removeLinks();
setTimeout(() => {
this.scrollToBottom();
}, 500); }
}, 500);
}
public async setLogLevel(): Promise<void> {
this.settingLogLevel = true;
try {
await this.daemonService.setLogLevel(this.setLogLevelLevel);
this.setLogLevelError = '';
this.setLogLevelSuccess = true;
} catch (error) {
this.setLogLevelSuccess = false;
this.setLogLevelError = `${error}`;
console.error(error);
}
this.settingLogLevel = false;
}
public async setLogHashRate(): Promise<void> {
this.settingLogHashRate = true;
try {
await this.daemonService.setLogHashRate(this.setLogHashRateEnabled);
this.setLogHashRateError = '';
this.setLogHashRateSuccess = true;
} catch(error) {
console.error(error);
this.setLogHashRateError = `${error}`;
this.setLogHashRateSuccess = false;
}
this.settingLogHashRate = false;
}
}

View file

@ -0,0 +1,18 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NetworkComponent } from './network.component';
import { CommonModule } from '@angular/common';
const routes: Routes = [
{
path: 'network',
component: NetworkComponent
}
];
@NgModule({
imports: [CommonModule, RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class NetworkRoutingModule { }

View file

@ -0,0 +1,18 @@
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Network</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<ul class="nav nav-pills m-3" id="pills-tab" role="tablist">
@for(navbarLink of navbarLinks; track navbarLink.name) {
<li class="nav-item mr-2" role="presentation">
<button [class]="navbarLink.selected ? 'nav-link active btn-sm' : 'nav-link btn-sm'" [id]="navbarLink.id" data-bs-toggle="pill" [attr.data-bs-target]="navbarLink.target" type="button" role="tab" [attr.aria-controls]="navbarLink.controls" [attr.aria-selected]="navbarLink.selected" [disabled]="navbarLink.disabled">{{navbarLink.name}}</button>
</li>
}
</ul>
</div>
</div>
<div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent">
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NetworkComponent } from './network.component';
describe('NetworkComponent', () => {
let component: NetworkComponent;
let fixture: ComponentFixture<NetworkComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NetworkComponent]
})
.compileComponents();
fixture = TestBed.createComponent(NetworkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,30 @@
import { AfterViewInit, Component } from '@angular/core';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { DaemonDataService } from '../../core/services';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
@Component({
selector: 'app-network',
templateUrl: './network.component.html',
styleUrl: './network.component.scss'
})
export class NetworkComponent implements AfterViewInit {
public daemonRunning: boolean = false;
public readonly navbarLinks: NavbarLink[];
constructor(private navbarService: NavbarService, private daemonData: DaemonDataService) {
this.navbarLinks = [
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-public-nodes-tab', '#pills-public-nodes', 'pills-public-nodes', false, 'Public Nodes')
];
}
public ngAfterViewInit(): void {
this.daemonRunning = this.daemonData.running;
this.navbarService.setLinks(this.navbarLinks);
}
}

View file

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NetworkRoutingModule } from './network-routing.module';
import { SharedModule } from '../../shared/shared.module';
import { NetworkComponent } from './network.component';
@NgModule({
declarations: [NetworkComponent],
imports: [
CommonModule,
SharedModule,
NetworkRoutingModule
]
})
export class NetworkModule { }

View file

@ -12,6 +12,7 @@
</div>
<div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-relay-tx" role="tabpanel" aria-labelledby="pills-relay-tx-tab" tabindex="0">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-12">
@ -57,6 +58,52 @@
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" [disabled]="!validTxIds()" (click)="onRelay()">Relay Tx</button>
</div>
<div class="tab-pane fade" id="pills-send-raw-tx" role="tabpanel" aria-labelledby="pills-send-raw-tx-tab" tabindex="0">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-12">
<div *ngIf="sendRawTxSuccess" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-send-check m-2"></i></h4>&nbsp;&nbsp;
<div>
Txs sent successfully
</div>
</div>
<div *ngIf="sendRawTxError" class="alert alert-danger d-flex align-items-center justify-content-center text-center" role="alert">
<h4><i class="bi bi-exclamation-triangle m-2"></i></h4>&nbsp;&nbsp;
<div>
{{sendRawTxError}}
</div>
</div>
<h4 class="mb-3">Broadcast a raw transaction to the network</h4>
<form class="needs-validation" novalidate="">
<div class="row g-3">
<div class="col-12">
<label for="send-raw-tx-tx-as-hex" class="form-label">Tx as hex</label>
<textarea [(ngModel)]="rawTxJsonString" [ngModelOptions]="{standalone: true}" type="text" class="form-control" id="send-raw-tx-tx-as-hex" placeholder="de6a3..."
rows="15" cols="15" ></textarea>
<div class="invalid-feedback">
Invalid transaction hex.
</div>
<small class="text-body-secondary">Full transaction information as hexadecimal string</small>
</div>
<div class="form-check form-switch col-md-6">
<label for="send-raw-tx-do-not-relay" class="form-check-label">Do not relay</label>
<input class="form-control form-check-input" type="checkbox" role="switch" id="send-raw-tx-do-not-relay" [checked]="sendRawTxDoNotRelay">
<br>
<small class="text-body-secondary">Stop relaying transaction to other nodes</small>
</div>
<hr class="my-4">
</div>
</form>
</div>
</div>
<button class="w-100 btn btn-primary btn-lg" type="button" (click)="sendRawTx()">Send Raw Tx</button>
</div>
<div class="tab-pane fade" id="pills-coinbase-tx-sum" role="tabpanel" aria-labelledby="pills-coinbase-tx-sum-tab" tabindex="0">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-12">
@ -160,7 +207,6 @@
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" (click)="onFlushFromCache()">Flush Bad Txs</button>
</div>
</div>

View file

@ -31,9 +31,16 @@ export class TransactionsComponent implements AfterViewInit {
public daemonRunning: boolean = false;
public rawTxJsonString: string = '';
public sendRawTxDoNotRelay: boolean = false;
public sendRawTxSuccess: boolean = false;
public sendRawTxError: string = '';
public sendingRawTx: boolean = false;
constructor(private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) {
this.navbarLinks = [
new NavbarLink('pills-relay-tx-tab', '#pills-relay-tx', 'pills-relay-tx', true, 'Relay Tx'),
new NavbarLink('pills-send-raw-tx-tab', '#pills-send-raw-tx', 'pills-send-raw-tx', false, 'Send Raw Tx'),
new NavbarLink('pills-tx-backlog-tab', '#pills-tx-backlog', 'pills-tx-backlog', false, 'Tx Backlog'),
new NavbarLink('pills-coinbase-tx-sum-tab', '#pills-coinbase-tx-sum', 'pills-coinbase-tx-sum', false, 'Coinbase Tx Sum'),
new NavbarLink('pills-flush-tx-pool-tab', '#pills-flush-tx-pool', 'pills-flush-tx-pool', false, 'Flush Tx Pool'),
@ -152,4 +159,41 @@ export class TransactionsComponent implements AfterViewInit {
this.getCoinbaseTxSumError = `${error}`;
}
}
public async sendRawTx(): Promise<void> {
this.sendingRawTx = true;
try {
const info = await this.daemonService.sendRawTransaction(this.rawTxJsonString, this.sendRawTxDoNotRelay);
if (info.doubleSpend) {
throw new Error('Transaction is double spend');
}
else if (info.feeTooLow) {
throw new Error('Fee is too low');
}
else if (info.invalidInput) {
throw new Error('Input is invalid');
}
else if (info.invalidOutput) {
throw new Error('Output is invalid');
}
else if (info.lowMixin) {
throw new Error('Mixin count is too low');
}
else if (info.overspend) {
throw new Error('Transaction uses more money than available')
}
else if (info.tooBig) {
throw new Error('Transaction size is too big');
}
this.sendRawTxSuccess = true;
this.sendRawTxError = '';
}
catch(error) {
this.sendRawTxError = `${error}`;
}
this.sendingRawTx = false;
}
}

View file

@ -37,6 +37,7 @@ export class SidebarComponent implements OnChanges {
new NavLink('Outputs', '/outputs', 'bi bi-circle-fill'),
new NavLink('Mining', '/mining', 'bi bi-minecart-loaded'),
new NavLink('Hard Fork Info', '/hardforkinfo', 'bi bi-signpost-split'),
new NavLink('Network', '/network', 'bi bi-hdd-network'),
new NavLink('XMRig', '/xmrig', 'icon-xr text-primary'),
new NavLink('Bans', '/bans', 'bi bi-ban', 'bottom'),
new NavLink('Logs', '/logs', 'bi bi-terminal', 'bottom'),

View file

@ -0,0 +1,20 @@
import { RPCRequest } from "./RPCRequest";
export class SetLogCategoriesRequest extends RPCRequest {
public override readonly method: 'set_log_categories' = 'set_log_categories';
public override readonly restricted: true = true;
public readonly categories: string;
constructor(categories: string) {
super();
this.categories = categories;
}
public toDictionary(): { [key: string]: any; } {
return {
'categories': this.categories
}
}
}

View file

@ -0,0 +1,19 @@
import { RPCRequest } from "./RPCRequest";
export class SetLogHashRateRequest extends RPCRequest {
public override readonly method: 'set_log_hash_rate' = 'set_log_hash_rate';
public override readonly restricted: boolean = true;
public readonly visible: boolean;
constructor(visible: boolean) {
super();
this.visible = visible;
}
public override toDictionary(): { [key: string]: any; } {
return {
'visible': this.visible
};
}
}

View file

@ -0,0 +1,23 @@
import { RPCRequest } from "./RPCRequest";
export class SetLogLevelRequest extends RPCRequest {
public override readonly method: 'set_log_level' = 'set_log_level';
public override readonly restricted: boolean = true;
public readonly level: number;
constructor(level: number) {
super();
if (level < 0 || level > 4) {
throw new Error(`Invalid log level provided: ${level}`);
}
this.level = level;
}
public toDictionary(): { [key: string]: any; } {
return {
'level': this.level
};
}
}

View file

@ -54,6 +54,9 @@ export { IsKeyImageSpentRequest } from "./IsKeyImageSpentRequest";
export { GetAltBlockHashesRequest } from "./GetAltBlockHashesRequest";
export { SaveBcRequest } from "./SaveBcRequest";
export { SetBootstrapDaemonRequest } from "./SetBootstrapDaemonRequest";
export { SetLogLevelRequest } from "./SetLogLevelRequest";
export { SetLogHashRateRequest } from "./SetLogHashRateRequest";
export { SetLogCategoriesRequest } from "./SetLogCategoriesRequest";
/**
* Restricted requests