Wide implementation

This commit is contained in:
everoddandeven 2024-10-03 22:35:30 +02:00
parent 131199113d
commit 91b38d1fb4
17 changed files with 795 additions and 595 deletions

View file

@ -122,7 +122,13 @@ function getMonerodVersion(monerodFilePath: string): void {
}
function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStreams {
const monerodPath = getMonerodPath();
//const monerodPath = getMonerodPath();
const monerodPath = commandOptions.shift();
if (!monerodPath) {
win?.webContents.send('monero-sterr', `Invalid monerod path provided: ${monerodPath}`);
throw Error("Invalid monerod path provided");
}
console.log("Starting monerod daemon with options: " + commandOptions.join(" "));

View file

@ -46,21 +46,6 @@ export class AppComponent {
private async load(): Promise<void> {
this.loading = true;
if (!window.indexedDB) {
console.log("Il tuo browser non supporta indexedDB");
}
else {
console.log("Browser supports IndexedDB");
var request = window.indexedDB.open("dati", 1);
console.log(request);
request.onsuccess = function(event: Event) {
if(event.target instanceof IDBOpenDBRequest) {
console.log(event.target.result)
}
};
}
try {
this.daemonRunning = await this.daemonService.isRunning();
}

View file

@ -1,6 +1,6 @@
import { EventEmitter, Injectable } from '@angular/core';
import { DaemonService } from './daemon.service';
import { BlockCount, BlockHeader, DaemonInfo, SyncInfo } from '../../../../common';
import { BlockCount, BlockHeader, Chain, DaemonInfo, SyncInfo } from '../../../../common';
@Injectable({
providedIn: 'root'
@ -31,6 +31,9 @@ export class DaemonDataService {
private _lastBlockHeader?: BlockHeader;
private _gettingLastBlockHeader: boolean = false;
private _altChains: Chain[] = [];
private _gettingAltChains: 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>();
@ -115,6 +118,14 @@ export class DaemonDataService {
return this._gettingLastBlockHeader;
}
public get AltChains(): Chain[] {
return this._altChains;
}
public get gettingAltChains(): boolean {
return this._gettingAltChains;
}
public setRefreshTimeout(ms: number = 5000): void {
this.refreshTimeoutMs = ms;
}
@ -144,6 +155,10 @@ export class DaemonDataService {
return Date.now() - this._lastRefresh <= this.refreshTimeoutMs;
}
private async getInfo(): Promise<void> {
}
private async refresh(): Promise<void> {
if (this.refreshing || this.tooEarlyForRefresh) {
return;
@ -157,6 +172,11 @@ export class DaemonDataService {
this._daemonRunning = await this.daemonService.isRunning();
this._firstRefresh = false;
if (!this._daemonRunning) {
this.syncEnd.emit();
return;
}
this._gettingDaemonInfo = true;
this._daemonInfo = await this.daemonService.getInfo();
this._gettingDaemonInfo = false;
@ -179,6 +199,10 @@ export class DaemonDataService {
if (firstRefresh) this._isBlockchainPruned = (await this.daemonService.pruneBlockchain(true)).pruned;
this._gettingIsBlockchainPruned = false;
this._gettingAltChains = true;
this._altChains = await this.daemonService.getAlternateChains();
this._gettingAltChains = false;
this._lastRefresh = Date.now();
} catch(error) {
console.error(error);
@ -187,6 +211,7 @@ export class DaemonDataService {
this._gettingBlockCount = false;
this._gettingLastBlockHeader = false;
this._gettingIsBlockchainPruned = false;
this._gettingAltChains = false;
this.syncError.emit(<Error>error);

View file

@ -98,6 +98,8 @@ export class DaemonService {
public readonly onDaemonStopStart: EventEmitter<void> = new EventEmitter<void>();
public readonly onDaemonStopEnd: EventEmitter<void> = new EventEmitter<void>();
private isRunningPromise?: Promise<boolean>;
private readonly headers: { [key: string]: string } = {
"Access-Control-Allow-Headers": "*", // this will allow all CORS requests
"Access-Control-Allow-Methods": 'POST,GET' // this states the allowed methods
@ -237,7 +239,8 @@ export class DaemonService {
return response;
}
catch (error) {
if (error instanceof HttpErrorResponse && error.status == 0) {
if (error instanceof HttpErrorResponse) {
if (error.status == 0) {
const wasRunning = this.daemonRunning;
this.daemonRunning = false;
@ -246,6 +249,9 @@ export class DaemonService {
}
}
throw new Error(error.message);
}
throw error;
}
}
@ -282,26 +288,37 @@ export class DaemonService {
}
public async isRunning(force: boolean = false): Promise<boolean> {
private async checkDaemonIsRunning(): Promise<boolean> {
try {
if (!force && this.daemonRunning != undefined) {
return this.daemonRunning;
}
await this.callRpc(new EmptyRpcRequest());
}
catch(error) {
if (error instanceof MethodNotFoundError) {
this.daemonRunning = true;
return this.daemonRunning;
return true;
}
console.error(error);
}
this.daemonRunning = false;
return this.daemonRunning;
return false;
}
public async isRunning(force: boolean = false): Promise<boolean> {
if (this.isRunningPromise) {
return await this.isRunningPromise;
}
if (!force && this.daemonRunning != undefined) {
return this.daemonRunning;
}
this.isRunningPromise = this.checkDaemonIsRunning();
this.daemonRunning = await this.isRunningPromise;
this.isRunningPromise = undefined;
return this.daemonRunning;
}
public async getBlock(heightOrHash: number | string, fillPowHash: boolean = false): Promise<Block> {
@ -678,6 +695,10 @@ export class DaemonService {
public async isKeyImageSpent(...keyImages: string[]): Promise<number[]> {
const response = await this.callRpc(new IsKeyImageSpentRequest(keyImages));
if (response.status != 'OK') {
throw new Error(response.status);
}
return response.spent_status;
}

View file

@ -423,6 +423,32 @@
</div>
<div class="tab-pane fade" id="pills-prune-blockchain" role="tabpanel" aria-labelledby="pills-prune-blockchain-tab" tabindex="0">
<div *ngIf="pruneBlockchainError != ''" 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>
{{pruneBlockchainError}}
</div>
</div>
<div *ngIf="blockchainPruned" 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 initiated blockchain pruning
</div>
</div>
<div class="alert alert-info d-flex text-center" role="alert">
<div>
'Pruning' allows node operators to save 2/3 of storage space while keeping the full transaction history </div>
</div>
<hr class="my-4">
<button *ngIf="!pruningBlockchain" class="w-100 btn btn-primary btn-lg" type="button" (click)="pruneBlockchain()">Prune Blockchain</button>
<button *ngIf="pruningBlockchain" class="w-100 btn btn-primary btn-lg" type="button" disabled>Pruning Blockchain ...</button>
</div>
<div class="tab-pane fade" id="pills-save-bc" role="tabpanel" aria-labelledby="pills-save-bc-tab" tabindex="0">
<div *ngIf="saveBlockchainError != ''" class="alert alert-danger d-flex align-items-center justify-content-center text-center" role="alert">

View file

@ -39,13 +39,18 @@ export class BlockchainComponent implements AfterViewInit {
public saveBlockchainError: string = '';
public blockchainSaved: boolean = false;
public pruningBlockchain: boolean = false;
public pruneBlockchainError: string = '';
public blockchainPruned: boolean = false;
constructor(private daemonService: DaemonService, private ngZone: NgZone) {
this.navbarLinks = [
new NavbarLink('pills-last-block-header-tab', '#pills-last-block-header', 'pills-last-block-header', true, 'Last Block Header', true),
new NavbarLink('pills-get-block-tab', '#pills-get-block', 'pills-get-block', false, 'Get Block', true),
new NavbarLink('pills-get-block-header-tab', '#pills-get-block-header', 'pills-get-block-header', false, 'Get Block Header', true),
new NavbarLink('pills-pop-blocks-tab', '#pills-pop-blocks', 'pills-pop-blocks', false, 'Pop Blocks', true),
new NavbarLink('pills-save-bc-tab', '#pills-save-bc', 'pills-save-bc', false, 'Save Blockchain', true)
new NavbarLink('pills-last-block-header-tab', '#pills-last-block-header', 'pills-last-block-header', true, 'Last Block Header'),
new NavbarLink('pills-get-block-tab', '#pills-get-block', 'pills-get-block', false, 'Get Block'),
new NavbarLink('pills-get-block-header-tab', '#pills-get-block-header', 'pills-get-block-header', false, 'Get Block Header'),
new NavbarLink('pills-pop-blocks-tab', '#pills-pop-blocks', 'pills-pop-blocks', false, 'Pop Blocks'),
new NavbarLink('pills-prune-blockchain-tab', '#pills-prune-blockchain', 'pills-prune-blockchain', false, 'Prune'),
new NavbarLink('pills-save-bc-tab', '#pills-save-bc', 'pills-save-bc', false, 'Save')
];
this.daemonService.onDaemonStatusChanged.subscribe((running) => {
@ -148,4 +153,18 @@ export class BlockchainComponent implements AfterViewInit {
this.savingBlockchain = false;
}
public async pruneBlockchain(): Promise<void> {
this.pruningBlockchain = true;
try {
await this.daemonService.pruneBlockchain(false);
this.blockchainPruned = true;
} catch(error) {
this.pruneBlockchainError = `${error}`;
this.blockchainPruned = false;
}
this.pruningBlockchain = false;
}
}

View file

@ -34,7 +34,13 @@ export class DetailComponent implements AfterViewInit {
}
private get targetHeight(): number {
return this.daemonData.syncInfo ? this.daemonData.syncInfo.targetHeight : 0;
const value = this.daemonData.syncInfo ? this.daemonData.syncInfo.targetHeight : 0;
if (value == 0 && this.height > 0) {
return this.height;
}
return value;
}
private get nextNeededPruningSeed(): number {
@ -90,7 +96,11 @@ export class DetailComponent implements AfterViewInit {
}
private get syncProgress(): string {
return `${(this.height*100/this.targetHeight).toFixed(2)} %`;
const targetHeight = this.targetHeight;
const height = this.height;
console.log(`Sync progress, height ${height},targetHeight ${targetHeight}`)
return `${(height*100/targetHeight).toFixed(2)} %`;
}
//#endregion

View file

@ -32,7 +32,137 @@
</table>
</div>
<div class="tab-pane fade" id="pills-outputs-histogram" role="tabpanel" aria-labelledby="pills-outputs-histogram-tab" tabindex="0">
<div *ngIf="getOutDistributionError != ''" 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>
{{getOutHistogramError}}
</div>
</div>
<div *ngIf="getOutHistogramResult != undefined" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
<div class="col-sm-12">
<h4 class="mb-3">Get a histogram of output amounts. For all amounts (possibly filtered by parameters), gives the number of outputs on the chain for that amount. RingCT outputs counts as 0 amount.</h4>
<form class="needs-validation" novalidate="">
<div class="row g-3">
<div class="col-12">
<label for="get-out-histogram-amounts" class="form-label">Amounts</label>
<textarea type="text" class="form-control" id="get-out-histogram-amounts" placeholder="[
'4323154546',
'5423442423',
... ,
'2346534525'
]"
rows="15" cols="15" [(ngModel)]="getOutHistogramAmountsJsonString" [ngModelOptions]="{standalone: true}"></textarea>
<small class="text-body-secondary">Array of unsigned int</small>
</div>
</div>
</form>
</div>
<div class="col-sm-6">
<label for="get-out-histogram-min-count" class="form-label">Min count</label>
<input type="number" min="0" class="form-control" id="get-out-histogram-min-count" placeholder="" [(ngModel)]="getOutHistogramMinCount" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary"></small>
</div>
<div class="col-sm-6">
<label for="get-out-histogram-max-count" class="form-label">Max count</label>
<input type="number" min="0" class="form-control" id="get-out-histogram-max-count" placeholder="" [(ngModel)]="getOutHistogramMaxCount" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary"></small>
</div>
<div class="form-check form-switch col-md-6">
<label for="get-out-histogram-unlocked" class="form-check-label">Unlocked</label>
<input class="form-control form-check-input" type="checkbox" role="switch" id="get-out-histogram-unlocked" [checked]="getOutHistogramUnlocked" [(ngModel)]="getOutHistogramUnlocked" [ngModelOptions]="{standalone: true}">
<br>
<small class="text-body-secondary"></small>
</div>
<div class="col-sm-6">
<label for="get-out-histogram-recent-cutoff" class="form-label">Recent cutoff</label>
<input type="number" min="0" class="form-control" id="get-out-histogram-recent-cutoff" placeholder="" [(ngModel)]="getOutHistogramRecentCutoff" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary"></small>
</div>
</div>
</div>
</div>
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!validOutDistributionAmounts" (click)="getOutHistogram()">Get Output Histogram</button>
</div>
<div class="tab-pane fade" id="pills-outputs-distribution" role="tabpanel" aria-labelledby="pills-outputs-distribution-tab" tabindex="0">
<div *ngIf="getOutDistributionError != ''" 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>
{{getOutDistributionError}}
</div>
</div>
<div *ngIf="getOutDistributionResult != undefined" class="alert alert-success d-flex align-items-center justify-content-center text-center" role="alert">
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
<div class="col-sm-12">
<h4 class="mb-3">Get Outputs</h4>
<form class="needs-validation" novalidate="">
<div class="row g-3">
<div class="col-12">
<label for="get-out-distribution" class="form-label">Amounts</label>
<textarea type="text" class="form-control" id="get-out-distribution" placeholder="[
'4323154546',
'5423442423',
... ,
'2346534525'
]"
rows="15" cols="15" [(ngModel)]="getOutDistributionAmountsJsonString" [ngModelOptions]="{standalone: true}"></textarea>
<small class="text-body-secondary">Array of unsigned int, amounts to look for</small>
</div>
</div>
</form>
</div>
<div class="col-sm-6">
<label for="get-out-distribution-from-height" class="form-label">From Height</label>
<input type="number" min="0" class="form-control" id="get-out-distribution-from-height" placeholder="" [(ngModel)]="getOutDistributionFromHeight" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Starting height to check from</small>
</div>
<div class="col-sm-6">
<label for="get-out-distribution-to-height" class="form-label">To Height</label>
<input type="number" min="0" class="form-control" id="get-out-distribution-to-height" placeholder="" [(ngModel)]="getOutDistributionToHeight" [ngModelOptions]="{standalone: true}">
<small class="text-body-secondary">Ending height to check up to</small>
</div>
<div class="form-check form-switch col-md-6">
<label for="get-out-distribution-cumulative" class="form-check-label">Cumulative</label>
<input class="form-control form-check-input" type="checkbox" role="switch" id="get-out-distribution-cumulative" [checked]="getOutDistributionCumulative" [(ngModel)]="getOutDistributionCumulative" [ngModelOptions]="{standalone: true}">
<br>
<small class="text-body-secondary">Cumulative result</small>
</div>
</div>
</div>
</div>
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!validOutDistributionAmounts" (click)="getOutDistribution()">Get Out Distribution</button>
</div>
<div class="tab-pane fade" id="pills-is-key-image-spent" role="tabpanel" aria-labelledby="pills-is-key-image-spent-tab" tabindex="0">
<div *ngIf="isKeyImageSpentError != ''" 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>
@ -40,6 +170,25 @@
</div>
</div>
<div [hidden]="!isKeyImageSpentResult">
<h4 class="mb-3">Key images spent status</h4>
<table
id="keyImagesTable"
data-toggle="keyImagesTable"
data-toolbar="#toolbar"
>
<thead>
<tr>
<th data-field="keyImage">Key Image</th>
<th data-field="spentStatus">Status</th>
</tr>
</thead>
</table>
<hr class="my-4">
</div>
<div class="row g-5 p-2">
<div class="cold-md-7 col-lg-12">
<div class="row gy-3">
@ -47,8 +196,9 @@
<div class="col-sm-12">
<label for="key-images" class="form-label">Key images</label>
<textarea [(ngModel)]="keyImages" [ngModelOptions]="{standalone: true}" type="text" class="form-control" id="key-images" placeholder=""
<textarea [(ngModel)]="keyImagesJsonString" [ngModelOptions]="{standalone: true}" type="text" class="form-control" id="key-images" placeholder=""
rows="15" cols="15" ></textarea>
<small class="text-body-secondary">List of key image hex strings to check.</small>
<div class="invalid-feedback">
Invalid key images.
</div>

View file

@ -2,7 +2,7 @@ import { AfterViewInit, Component, NgZone } from '@angular/core';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonService } from '../../core/services/daemon/daemon.service';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { Output } from '../../../common';
import { HistogramEntry, Output, OutputDistribution } from '../../../common';
@Component({
selector: 'app-outputs',
@ -17,11 +17,63 @@ export class OutputsComponent implements AfterViewInit {
public getOutsJsonString: string = '';
public getOutsGetTxId: boolean = false;
public keyImages: string = '';
public keyImagesJsonString: string = '';
public isKeyImageSpentError: string = '';
public isKeyImageSpentResult?: boolean;
public isKeyImageSpentResult?: { keyImage: string, spentStatus: string }[];
public gettingKeyImages: boolean = false;
public get validKeyImages(): boolean {
try {
const keyImages: string[] = JSON.parse(this.keyImagesJsonString);
if (!Array.isArray(keyImages)) {
return false;
}
keyImages.forEach((keyImage: string) => {
if (typeof keyImage != 'string') {
throw new Error();
}
});
return true;
} catch(error) {
return false;
}
}
public get keyImages(): string[] {
if (!this.validKeyImages) {
return [];
}
return JSON.parse(this.keyImagesJsonString);
}
public getOutHistogramAmountsJsonString: string = '';
public getOutHistogramMinCount: number = 0;
public getOutHistogramMaxCount: number = 0;
public getOutHistogramUnlocked: boolean = false;
public getOutHistogramRecentCutoff: number = 0;
public getOutHistogramResult?: HistogramEntry[];
public getOutHistogramError: string = '';
public gettingOutHistogram: boolean = false;
public getOutDistributionAmountsJsonString: string = '';
public getOutDistributionFromHeight: number = 0;
public getOutDistributionToHeight: number = 0;
public getOutDistributionCumulative: boolean = false;
public getOutDistributionResult?: OutputDistribution[];
public getOutDistributionError: string = '';
public get getOutDistributionAmounts(): number[] {
if (!this.validOutDistributionAmounts) {
return [];
}
return JSON.parse(this.getOutDistributionAmountsJsonString);
}
constructor(private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) {
this.navbarLinks = [
new NavbarLink('pills-outputs-overview-tab', '#pills-outputs-overview', 'outputs-overview', true, 'Overview'),
@ -67,7 +119,7 @@ export class OutputsComponent implements AfterViewInit {
}
}
public validOuts(): boolean {
public get validOuts(): boolean {
try {
const _outs: any[] = JSON.parse(this.getOutsJsonString);
@ -93,11 +145,80 @@ export class OutputsComponent implements AfterViewInit {
}
public async isKeyImageSpent(): Promise<void> {
public get validOutDistributionAmounts(): boolean {
try {
const amounts: number[] = JSON.parse(this.getOutDistributionAmountsJsonString);
if(!Array.isArray(amounts)) {
return false;
}
amounts.forEach((amount) => {
if (typeof amount != 'number' || amount <= 0) throw new Error("");
})
return true;
}
catch(error) {
return false;
}
}
public async getOutDistribution(): Promise<void> {
try
{
const amounts = this.getOutDistributionAmounts;
const cumulative = this.getOutDistributionCumulative;
const fromHeight = this.getOutDistributionFromHeight;
const toHeight = this.getOutDistributionToHeight;
this.getOutDistributionResult = await this.daemonService.getOutputDistribution(amounts, cumulative, fromHeight, toHeight);
this.getOutDistributionError = '';
}
catch(error) {
this.getOutDistributionError = `${error}`;
}
}
public async getOutHistogram(): Promise<void> {
}
public async isKeyImageSpent(): Promise<void> {
this.gettingKeyImages = true;
try {
const keyImages: string[] = this.keyImages;
const spentList: number[] = await this.daemonService.isKeyImageSpent(...keyImages);
if (keyImages.length != spentList.length) {
throw new Error("Invalid spent list size response");
}
this.isKeyImageSpentResult = [];
for(let i = 0; i < keyImages.length; i++) {
const ki = keyImages[i];
const spentStatus = spentList[i];
this.isKeyImageSpentResult.push({
keyImage: ki,
spentStatus: spentStatus == 0 ? 'unspent' : spentStatus == 1 ? 'spent in blockchain' : spentStatus == 2 ? 'spent in tx pool' : 'unknown'
})
const $table = $('#keyImagesTable');
$table.bootstrapTable({});
$table.bootstrapTable('load', this.isKeyImageSpentResult);
this.isKeyImageSpentError = '';
}
} catch(error) {
this.isKeyImageSpentError = `${error}`;
this.isKeyImageSpentResult = undefined;
}
this.gettingKeyImages = false;
}
public async load() {
await this.getOuts();
}
}

View file

@ -1,6 +1,7 @@
<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">Settings</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<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">
@ -9,12 +10,39 @@
}
</ul>
</div>
</div>
</div>
<div *ngIf="!loading" class="tab-content" id="pills-settings-tabContent">
<div class="tab-pane fade show active" id="pills-rpc" role="tabpanel" aria-labelledby="pills-rpc-tab" tabindex="0">
<div class="tab-pane fade show active" id="pills-general" role="tabpanel" aria-labelledby="pills-general-tab" tabindex="0">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<h4 class="mb-3">Node</h4>
<div class="row gy-3">
<div class="col-md-12">
<label for="general-monerod-path" 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">
<small class="text-body-secondary">Path to monerod executable</small>
</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 class="tab-pane fade" id="pills-rpc" role="tabpanel" aria-labelledby="pills-rpc-tab" tabindex="0">
<div class="row g-5 m-2">
<div class="col-md-7 col-lg-10">
<h4 class="mb-3">General</h4>

View file

@ -1,9 +1,8 @@
import { AfterViewInit, Component } from '@angular/core';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { NavigationEnd, NavigationStart, Router } from '@angular/router';
import { Router } from '@angular/router';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonSettings } from '../../../common/DaemonSettings';
import { FormsModule, NgModel } from '@angular/forms';
import { DaemonService } from '../../core/services/daemon/daemon.service';
@Component({
@ -27,7 +26,8 @@ export class SettingsComponent implements AfterViewInit {
this.loading = true;
this.navbarLinks = [
new NavbarLink('pills-rpc-tab', '#pills-rpc', 'pills-rpc', true, 'RPC'),
new NavbarLink('pills-general-tab', '#pills-general', 'pills-general', true, 'General'),
new NavbarLink('pills-rpc-tab', '#pills-rpc', 'pills-rpc', false, 'RPC'),
new NavbarLink('pills-p2p-tab', '#pills-p2p', 'pills-p2p', false, 'P2P'),
new NavbarLink('pills-blockchain-tab', '#pills-blockchain', 'pills-blockchain', false, 'Blockchain'),
new NavbarLink('pills-mining-tab', '#pills-mining', 'pills-mining', false, 'Mining'),
@ -46,13 +46,6 @@ export class SettingsComponent implements AfterViewInit {
this.rpcLoginPassword = '';
}
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
if (event.url != '/settings') return;
this.onNavigationEnd();
}
});
this.load();
}
@ -70,12 +63,12 @@ export class SettingsComponent implements AfterViewInit {
return true;
}
return false;
}
ngAfterViewInit(): void {
this.navbarService.setLinks(this.navbarLinks);
this.navbarService.enableLinks();
}
public OnOfflineChange() {
@ -136,8 +129,13 @@ export class SettingsComponent implements AfterViewInit {
}
}
private onNavigationEnd(): void {
public onMonerodPathChange(): void {
if (document) {
const element = <HTMLInputElement>document.getElementById('general-monerod-path');
if (element.files) {
this.currentSettings.monerodPath = element.files[0].path;
}
}
}
public async OnSave(): Promise<void> {
@ -150,220 +148,3 @@ export class SettingsComponent implements AfterViewInit {
this.originalSettings = this.currentSettings.clone();
}
}
/**
* --log-file arg (=/home/sidney/.bitmonero/bitmonero.log, /home/sidney/.bitmonero/testnet/bitmonero.log if 'testnet', /home/sidney/.bitmonero/stagenet/bitmonero.log if 'stagenet')
Specify log file
--log-level arg
--max-log-file-size arg (=104850000) Specify maximum log file size [B]
--max-log-files arg (=50) Specify maximum number of rotated log
files to be saved (no limit by setting
to 0)
--max-concurrency arg (=0) Max number of threads to use for a
parallel job
--proxy arg Network communication through proxy:
<socks-ip:port> i.e. "127.0.0.1:9050"
--proxy-allow-dns-leaks Allow DNS leaks outside of proxy
--public-node Allow other users to use the node as a
remote (restricted RPC mode, view-only
commands) and advertise it over P2P
--zmq-rpc-bind-ip arg (=127.0.0.1) IP for ZMQ RPC server to listen on
--zmq-rpc-bind-port arg (=18082, 28082 if 'testnet', 38082 if 'stagenet')
Port for ZMQ RPC server to listen on
--zmq-pub arg Address for ZMQ pub - tcp://ip:port or
ipc://path
--no-zmq Disable ZMQ RPC server
--data-dir arg (=/home/sidney/.bitmonero, /home/sidney/.bitmonero/testnet if 'testnet', /home/sidney/.bitmonero/stagenet if 'stagenet')
Specify data directory
--test-drop-download For net tests: in download, discard ALL
blocks instead checking/saving them
(very fast)
--test-drop-download-height arg (=0) Like test-drop-download but discards
only after around certain height
--testnet Run on testnet. The wallet must be
launched with --testnet flag.
--stagenet Run on stagenet. The wallet must be
launched with --stagenet flag.
--regtest Run in a regression testing mode.
--keep-fakechain Don't delete any existing database when
in fakechain mode.
--fixed-difficulty arg (=0) Fixed difficulty used for testing.
--enforce-dns-checkpointing checkpoints from DNS server will be
enforced
--prep-blocks-threads arg (=4) Max number of threads to use when
preparing block hashes in groups.
--fast-block-sync arg (=1) Sync up most of the way by using
embedded, known block hashes.
--show-time-stats arg (=0) Show time-stats when processing
blocks/txs and disk synchronization.
--block-sync-size arg (=0) How many blocks to sync at once during
chain synchronization (0 = adaptive).
--check-updates arg (=notify) Check for new versions of monero:
[disabled|notify|download|update]
--fluffy-blocks Relay blocks as fluffy blocks
(obsolete, now default)
--no-fluffy-blocks Relay blocks as normal blocks
--test-dbg-lock-sleep arg (=0) Sleep time in ms, defaults to 0 (off),
used to debug before/after locking
mutex. Values 100 to 1000 are good for
tests.
--offline Do not listen for peers, nor connect to
any
--disable-dns-checkpoints Do not retrieve checkpoints from DNS
--block-download-max-size arg (=0) Set maximum size of block download
queue in bytes (0 for default)
--sync-pruned-blocks Allow syncing from nodes with only
pruned blocks
--max-txpool-weight arg (=648000000) Set maximum txpool weight in bytes.
--block-notify arg Run a program for each new block, '%s'
will be replaced by the block hash
--prune-blockchain Prune blockchain
--reorg-notify arg Run a program for each reorg, '%s' will
be replaced by the split height, '%h'
will be replaced by the new blockchain
height, '%n' will be replaced by the
number of new blocks in the new chain,
and '%d' will be replaced by the number
of blocks discarded from the old chain
--block-rate-notify arg Run a program when the block rate
undergoes large fluctuations. This
might be a sign of large amounts of
hash rate going on and off the Monero
network, and thus be of potential
interest in predicting attacks. %t will
be replaced by the number of minutes
for the observation window, %b by the
number of blocks observed within that
window, and %e by the number of blocks
that was expected in that window. It is
suggested that this notification is
used to automatically increase the
number of confirmations required before
a payment is acted upon.
--keep-alt-blocks Keep alternative blocks on restart
--extra-messages-file arg Specify file for extra messages to
include into coinbase transactions
--start-mining arg Specify wallet address to mining for
--mining-threads arg Specify mining threads count
--bg-mining-enable enable background mining
--bg-mining-ignore-battery if true, assumes plugged in when unable
to query system power status
--bg-mining-min-idle-interval arg Specify min lookback interval in
seconds for determining idle state
--bg-mining-idle-threshold arg Specify minimum avg idle percentage
over lookback interval
--bg-mining-miner-target arg Specify maximum percentage cpu use by
miner(s)
--db-sync-mode arg (=fast:async:250000000bytes)
Specify sync option, using format
[safe|fast|fastest]:[sync|async]:[<nblo
cks_per_sync>[blocks]|<nbytes_per_sync>
[bytes]].
--db-salvage Try to salvage a blockchain database if
it seems corrupted
--p2p-bind-ip arg (=0.0.0.0) Interface for p2p network protocol
(IPv4)
--p2p-bind-ipv6-address arg (=::) Interface for p2p network protocol
(IPv6)
--p2p-bind-port arg (=18080, 28080 if 'testnet', 38080 if 'stagenet')
Port for p2p network protocol (IPv4)
--p2p-bind-port-ipv6 arg (=18080, 28080 if 'testnet', 38080 if 'stagenet')
Port for p2p network protocol (IPv6)
--p2p-use-ipv6 Enable IPv6 for p2p
--p2p-ignore-ipv4 Ignore unsuccessful IPv4 bind for p2p
--p2p-external-port arg (=0) External port for p2p network protocol
(if port forwarding used with NAT)
--allow-local-ip Allow local ip add to peer list, mostly
in debug purposes
--add-peer arg Manually add peer to local peerlist
--add-priority-node arg Specify list of peers to connect to and
attempt to keep the connection open
--add-exclusive-node arg Specify list of peers to connect to
only. If this option is given the
options add-priority-node and seed-node
are ignored
--seed-node arg Connect to a node to retrieve peer
addresses, and disconnect
--tx-proxy arg Send local txes through proxy:
<network-type>,<socks-ip:port>[,max_con
nections][,disable_noise] i.e.
"tor,127.0.0.1:9050,100,disable_noise"
--anonymous-inbound arg <hidden-service-address>,<[bind-ip:]por
t>[,max_connections] i.e.
"x.onion,127.0.0.1:18083,100"
--ban-list arg Specify ban list file, one IP address
per line
--hide-my-port Do not announce yourself as peerlist
candidate
--no-sync Don't synchronize the blockchain with
other peers
--enable-dns-blocklist Apply realtime blocklist from DNS
--no-igd Disable UPnP port mapping
--igd arg (=delayed) UPnP port mapping (disabled, enabled,
delayed)
--out-peers arg (=-1) set max number of out peers
--in-peers arg (=-1) set max number of in peers
--tos-flag arg (=-1) set TOS flag
--limit-rate-up arg (=2048) set limit-rate-up [kB/s]
--limit-rate-down arg (=8192) set limit-rate-down [kB/s]
--limit-rate arg (=-1) set limit-rate [kB/s]
--pad-transactions Pad relayed transactions to help defend
against traffic volume analysis
--max-connections-per-ip arg (=1) Maximum number of connections allowed
from the same IP address
--rpc-bind-port arg (=18081, 28081 if 'testnet', 38081 if 'stagenet')
Port for RPC server
--rpc-restricted-bind-port arg Port for restricted RPC server
--restricted-rpc Restrict RPC to view only commands and
do not return privacy sensitive data in
RPC calls
--bootstrap-daemon-address arg URL of a 'bootstrap' remote daemon that
the connected wallets can use while
this daemon is still not fully synced.
Use 'auto' to enable automatic public
nodes discovering and bootstrap daemon
switching
--bootstrap-daemon-login arg Specify username:password for the
bootstrap daemon login
--bootstrap-daemon-proxy arg <ip>:<port> socks proxy to use for
bootstrap daemon connections
--rpc-bind-ip arg (=127.0.0.1) Specify IP to bind RPC server
--rpc-bind-ipv6-address arg (=::1) Specify IPv6 address to bind RPC server
--rpc-restricted-bind-ip arg (=127.0.0.1)
Specify IP to bind restricted RPC
server
--rpc-restricted-bind-ipv6-address arg (=::1)
Specify IPv6 address to bind restricted
RPC server
--rpc-use-ipv6 Allow IPv6 for RPC
--rpc-ignore-ipv4 Ignore unsuccessful IPv4 bind for RPC
--rpc-login arg Specify username[:password] required
for RPC server
--confirm-external-bind Confirm rpc-bind-ip value is NOT a
loopback (local) IP
--rpc-access-control-origins arg Specify a comma separated list of
origins to allow cross origin resource
sharing
--rpc-ssl arg (=autodetect) Enable SSL on RPC connections:
enabled|disabled|autodetect
--rpc-ssl-private-key arg Path to a PEM format private key
--rpc-ssl-certificate arg Path to a PEM format certificate
--rpc-ssl-ca-certificates arg Path to file containing concatenated
PEM format certificate(s) to replace
system CA(s).
--rpc-ssl-allowed-fingerprints arg List of certificate fingerprints to
allow
--rpc-ssl-allow-chained Allow user (via --rpc-ssl-certificates)
chain certificates
--disable-rpc-ban Do not ban hosts on RPC errors
--rpc-ssl-allow-any-cert Allow any peer certificate
--rpc-payment-address arg Restrict RPC to clients sending
micropayment to this address
--rpc-payment-difficulty arg (=1000) Restrict RPC to clients sending
micropayment at this difficulty
--rpc-payment-credits arg (=100) Restrict RPC to clients sending
micropayment, yields that many credits
per payment
--rpc-payment-allow-free-loopback Allow free access from the loopback
address (ie, the local host)
*/

View file

@ -1,11 +1,13 @@
<div *ngIf="!daemonRunning" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
<h2 *ngIf="!startingDaemon"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not running</h2>
<p *ngIf="!startingDaemon">Start monero daemon</p>
<h2 *ngIf="!startingDaemon && daemonConfigured"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not running</h2>
<h2 *ngIf="!startingDaemon && !daemonConfigured"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not configured</h2>
<p *ngIf="!startingDaemon && daemonConfigured">Start monero daemon</p>
<p *ngIf="!startingDaemon && !daemonConfigured">Configure monero daemon</p>
<h2 *ngIf="startingDaemon"><i class="bi bi-play-fill m-4"></i> Daemon is starting</h2>
<p *ngIf="startingDaemon">Starting monero daemon</p>
<button *ngIf="!startingDaemon" class="btn btn-outline-light" type="button" (click)="startDaemon()"><i class="bi bi-play-fill"></i> Start</button>
<button *ngIf="!startingDaemon && daemonConfigured" class="btn btn-outline-light" type="button" (click)="startDaemon()"><i class="bi bi-play-fill"></i> Start</button>
<button *ngIf="startingDaemon" class="btn btn-outline-light" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Starting monerod

View file

@ -9,6 +9,7 @@ import { DaemonService } from '../../../core/services/daemon/daemon.service';
export class DaemonNotRunningComponent {
public daemonRunning: boolean = false;
public daemonConfigured: boolean = false;
public get startingDaemon(): boolean {
return this.daemonService.starting;
@ -24,6 +25,9 @@ export class DaemonNotRunningComponent {
});
this.daemonService.isRunning().then((running: boolean) => {
this.ngZone.run(() => this.daemonRunning = running);
});
this.daemonService.getSettings().then((settings) => {
this.daemonConfigured = settings.monerodPath != '';
})
}

View file

@ -18,6 +18,12 @@ export class NavbarService {
this.daemonRunning = running;
if (!running) this.disableLinks();
if (running) this.enableLinks();
});
this.daemonService.isRunning().then((running: boolean) => {
this.daemonRunning = running;
if (!running) this.disableLinks();
if (running) this.enableLinks();
})
}
@ -44,7 +50,10 @@ export class NavbarService {
}
public enableLinks(): void {
this._navbarLinks.forEach((link) => link.disabled = false);
this._navbarLinks.forEach((link, index: number) => {
link.disabled = false
link.selected = index == 0;
});
}
}

View file

@ -1,64 +1,73 @@
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
}
html {
html {
height: -webkit-fill-available;
}
}
main {
main {
height: 100vh;
height: -webkit-fill-available;
max-height: 100vh;
overflow-x: auto;
overflow-y: hidden;
}
}
.dropdown-toggle { outline: 0; }
.dropdown-toggle { outline: 0; }
.btn-toggle {
.btn-toggle {
padding: .25rem .5rem;
font-weight: 600;
color: var(--bs-emphasis-color);
background-color: transparent;
}
.btn-toggle:hover,
.btn-toggle:focus {
}
.btn-toggle:hover,
.btn-toggle:focus {
color: rgba(var(--bs-emphasis-color-rgb), .85);
background-color: var(--bs-tertiary-bg);
}
}
.btn-toggle::before {
.btn-toggle::before {
width: 1.25em;
line-height: 0;
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
transition: transform .35s ease;
transform-origin: .5em 50%;
}
}
[data-bs-theme="dark"] .btn-toggle::before {
[data-bs-theme="dark"] .btn-toggle::before {
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%28255,255,255,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
}
}
.btn-toggle[aria-expanded="true"] {
.btn-toggle[aria-expanded="true"] {
color: rgba(var(--bs-emphasis-color-rgb), .85);
}
.btn-toggle[aria-expanded="true"]::before {
}
.btn-toggle[aria-expanded="true"]::before {
transform: rotate(90deg);
}
}
.btn-toggle-nav a {
.btn-toggle-nav a {
padding: .1875rem .5rem;
margin-top: .125rem;
margin-left: 1.25rem;
}
.btn-toggle-nav a:hover,
.btn-toggle-nav a:focus {
}
.btn-toggle-nav a:hover,
.btn-toggle-nav a:focus {
background-color: var(--bs-tertiary-bg);
}
}
.scrollarea {
.scrollarea {
overflow-y: auto;
}
}
.icon-xr {
display: inline-block;
width: 16px;
height: 16px;
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><text x="0" y="12" font-family="Arial, sans-serif" font-size="12" fill="%23ff9A85" font-weight="bold">Xr</text></svg>');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}

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('XMRig', '/xmrig', 'icon-xr text-primary'),
new NavLink('Bans', '/bans', 'bi bi-ban', 'bottom'),
new NavLink('Logs', '/logs', 'bi bi-terminal', 'bottom'),
new NavLink('Version', '/version', 'bi bi-git', 'bottom'),

View file

@ -1,4 +1,6 @@
export class DaemonSettings {
public monerodPath: string = '';
public logFile: string = '';
public logLevel: number = 0;
public maxLogFileSize: number = 104850000;
@ -144,6 +146,7 @@ export class DaemonSettings {
public toCommandOptions(): string[] {
const options: string[] = [];
if (this.monerodPath != '') options.push(this.monerodPath);
if (this.mainnet) options.push(`--mainnet`);
else if (this.testnet) options.push(`--testnet`);