mirror of
https://github.com/everoddandeven/monerod-gui.git
synced 2024-12-23 03:59:27 +00:00
Wide implementation
This commit is contained in:
parent
131199113d
commit
91b38d1fb4
17 changed files with 795 additions and 595 deletions
|
@ -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(" "));
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
<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>
|
||||
<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">
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
||||
*/
|
|
@ -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
|
||||
|
|
|
@ -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 != '';
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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'),
|
||||
|
|
|
@ -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`);
|
||||
|
|
Loading…
Reference in a new issue