mirror of
https://github.com/everoddandeven/monerod-gui.git
synced 2024-12-23 03:59:27 +00:00
Non json rpc requests implementation
This commit is contained in:
parent
696d0b5c86
commit
bb694c5d7a
59 changed files with 1834 additions and 197 deletions
17
app/main.ts
17
app/main.ts
|
@ -96,22 +96,13 @@ function execMoneroDaemon(configFilePath: string): ChildProcess {
|
|||
return monerodProcess;
|
||||
}
|
||||
|
||||
function startMoneroDaemon(configFilePath: string): ChildProcessWithoutNullStreams {
|
||||
function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStreams {
|
||||
const monerodPath = path.resolve(__dirname, monerodFilePath);
|
||||
|
||||
const args = [
|
||||
'--testnet',
|
||||
'--fast-block-sync', '1',
|
||||
'--prune-blockchain',
|
||||
'--sync-pruned-blocks',
|
||||
'--confirm-external-bind',
|
||||
'--max-concurrency', '1',
|
||||
'--log-level', '1',
|
||||
'--rpc-access-control-origins=*'
|
||||
];
|
||||
|
||||
console.log("Starting monerod daemon with options: " + commandOptions.join(" "));
|
||||
|
||||
// Avvia il processo usando spawn
|
||||
const monerodProcess = spawn(monerodPath, args);
|
||||
const monerodProcess = spawn(monerodPath, commandOptions);
|
||||
|
||||
// Gestisci l'output di stdout in streaming
|
||||
monerodProcess.stdout.on('data', (data) => {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { DetailRoutingModule } from './pages/detail/detail-routing.module';
|
|||
import { HardForkInfoRoutingModule } from './pages/hard-fork-info/hard-fork-info-routing.module';
|
||||
import { SettingsModule } from './pages/settings/settings.module';
|
||||
import { TransactionsModule } from './pages/transactions/transactions.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
@ -23,6 +24,7 @@ const routes: Routes = [
|
|||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot(routes, {}),
|
||||
FormsModule,
|
||||
HomeRoutingModule,
|
||||
DetailRoutingModule,
|
||||
TransactionsModule,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<app-navbar></app-navbar>
|
||||
<div class="d-flex">
|
||||
<app-sidebar></app-sidebar>
|
||||
<app-sidebar [isDaemonRunning]="daemonRunning"></app-sidebar>
|
||||
<div class="col-md-10">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Component } from '@angular/core';
|
|||
import { ElectronService } from './core/services';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { APP_CONFIG } from '../environments/environment';
|
||||
import { DaemonService } from './core/services/daemon/daemon.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -9,9 +10,13 @@ import { APP_CONFIG } from '../environments/environment';
|
|||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent {
|
||||
public loading: boolean;
|
||||
public daemonRunning: boolean;
|
||||
|
||||
constructor(
|
||||
private electronService: ElectronService,
|
||||
private translate: TranslateService
|
||||
private translate: TranslateService,
|
||||
private daemonService: DaemonService
|
||||
) {
|
||||
this.translate.setDefaultLang('en');
|
||||
console.log('APP_CONFIG', APP_CONFIG);
|
||||
|
@ -24,5 +29,22 @@ export class AppComponent {
|
|||
} else {
|
||||
console.log('Run in browser');
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
this.daemonRunning = false;
|
||||
this.load();
|
||||
}
|
||||
|
||||
private async load(): Promise<void> {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
this.daemonRunning = await this.daemonService.isRunning();
|
||||
}
|
||||
catch(error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { MiningModule } from './pages/mining/mining.module';
|
|||
import { TransactionsModule } from './pages/transactions/transactions.module';
|
||||
import { OutputsModule } from './pages/outputs/outputs.module';
|
||||
import { SidebarComponent } from './shared/components';
|
||||
import { SettingsModule } from './pages/settings/settings.module';
|
||||
|
||||
// AoT requires an exported function for factories
|
||||
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
|
||||
|
@ -40,6 +41,7 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
|
|||
MiningModule,
|
||||
TransactionsModule,
|
||||
OutputsModule,
|
||||
SettingsModule,
|
||||
TranslateModule,
|
||||
AppRoutingModule,
|
||||
TranslateModule.forRoot({
|
||||
|
|
|
@ -7,8 +7,8 @@ import {
|
|||
SubmitBlockRequest, GenerateBlocksRequest, GetLastBlockHeaderRequest,
|
||||
GetBlockHeaderByHashRequest, GetBlockHeaderByHeightRequest, GetBlockHeadersRangeRequest,
|
||||
GetConnectionsRequest, GetInfoRequest, HardForkInfoRequest, SetBansRequest, GetBansRequest,
|
||||
BannedRequest, FlushTxPoolRequest, GetOutputHistogramRequest,
|
||||
SyncInfoRequest,
|
||||
BannedRequest, FlushTxPoolRequest, GetOutputHistogramRequest, GetCoinbaseTxSumRequest,
|
||||
SyncInfoRequest, GetOutsRequest,
|
||||
GetVersionRequest,
|
||||
GetFeeEstimateRequest,
|
||||
GetAlternateChainsRequest,
|
||||
|
@ -17,7 +17,28 @@ import {
|
|||
CalculatePoWHashRequest,
|
||||
FlushCacheRequest,
|
||||
GetMinerDataRequest,
|
||||
EmptyRpcRequest
|
||||
EmptyRpcRequest, RPCRequest,
|
||||
AddAuxPoWRequest,
|
||||
GetOutputDistributionRequest,
|
||||
GetBlockRequest,
|
||||
UpdateRequest,
|
||||
PopBlocksRequest,
|
||||
GetTransactionPoolHashesRequest,
|
||||
GetTransactionPoolHashesBinaryRequest,
|
||||
GetPublicNodesRequest,
|
||||
GetNetStatsRequest,
|
||||
InPeersRequest,
|
||||
OutPeersRequest,
|
||||
SetLimitRequest,
|
||||
StopDaemonRequest,
|
||||
MiningStatusRequest,
|
||||
StopMiningRequest,
|
||||
StartMiningRequest,
|
||||
SendRawTransactionRequest,
|
||||
IsKeyImageSpentRequest,
|
||||
GetAltBlockHashesRequest,
|
||||
SaveBcRequest,
|
||||
SetBootstrapDaemonRequest
|
||||
} from '../../../../common/request';
|
||||
import { BlockTemplate } from '../../../../common/BlockTemplate';
|
||||
import { GeneratedBlocks } from '../../../../common/GeneratedBlocks';
|
||||
|
@ -37,14 +58,28 @@ import { BlockchainPruneInfo } from '../../../../common/BlockchainPruneInfo';
|
|||
import { MinerData } from '../../../../common/MinerData';
|
||||
import { CoreIsBusyError } from '../../../../common/error';
|
||||
import { ElectronService } from '../electron/electron.service';
|
||||
import { AddedAuxPow } from '../../../../common/AddedAuxPow';
|
||||
import { AuxPoW } from '../../../../common/AuxPoW';
|
||||
import { OutputDistribution } from '../../../../common/OutputDistribution';
|
||||
import { CoinbaseTxSum } from '../../../../common/CoinbaseTxSum';
|
||||
import { Block } from '../../../../common/Block';
|
||||
import { Output } from '../../../../common/Output';
|
||||
import { OutKey } from '../../../../common/OutKey';
|
||||
import { UpdateInfo } from '../../../../common/UpdateInfo';
|
||||
import { PublicNode } from '../../../../common/PublicNode';
|
||||
import { NetStats } from '../../../../common/NetStats';
|
||||
import { MiningStatus } from '../../../../common/MiningStatus';
|
||||
import { TxInfo } from '../../../../common/TxInfo';
|
||||
import { DaemonSettings } from '../../../../common/DaemonSettings';
|
||||
import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DaemonService {
|
||||
private daemonRunning?: boolean;
|
||||
private readonly configFilePath: string = './config';
|
||||
private url: string = "http://127.0.0.1:28081";
|
||||
public settings: DaemonSettings;
|
||||
//private url: string = "http://node2.monerodevs.org:28089";
|
||||
//private url: string = "https://testnet.xmr.ditatompel.com";
|
||||
//private url: string = "https://xmr.yemekyedim.com:18081";
|
||||
|
@ -55,10 +90,66 @@ export class DaemonService {
|
|||
"Access-Control-Allow-Methods": 'POST,GET' // this states the allowed methods
|
||||
};
|
||||
|
||||
constructor(private httpClient: HttpClient, private electronService: ElectronService) { }
|
||||
constructor(private httpClient: HttpClient, private electronService: ElectronService) {
|
||||
this.settings = this.loadSettings();
|
||||
}
|
||||
|
||||
private async callJsonRpc(params: JsonRPCRequest): Promise<{ [key: string]: any }> {
|
||||
return await firstValueFrom<{ [key: string]: any }>(this.httpClient.post(`${this.url}/json_rpc`, params.toDictionary(), this.headers));
|
||||
private loadSettings(): DaemonSettings {
|
||||
/*
|
||||
const args = [
|
||||
'--testnet',
|
||||
'--fast-block-sync', '1',
|
||||
'--prune-blockchain',
|
||||
'--sync-pruned-blocks',
|
||||
'--confirm-external-bind',
|
||||
'--max-concurrency', '1',
|
||||
'--log-level', '1',
|
||||
'--rpc-access-control-origins=*'
|
||||
];
|
||||
*/
|
||||
const settings = new DaemonSettings();
|
||||
settings.testnet = true;
|
||||
settings.fastBlockSync = true;
|
||||
settings.pruneBlockchain = true;
|
||||
settings.syncPrunedBlocks = true;
|
||||
settings.confirmExternalBind = true;
|
||||
settings.logLevel = 1;
|
||||
settings.rpcAccessControlOrigins = "*";
|
||||
return settings;
|
||||
}
|
||||
|
||||
private raiseRpcError(error: { code: number, message: string }): void {
|
||||
|
||||
if (error.code == -9) {
|
||||
throw new CoreIsBusyError();
|
||||
}
|
||||
else if (error.code == -32601) {
|
||||
throw new MethodNotFoundError();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error(error.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async callRpc(request: RPCRequest): Promise<{ [key: string]: any }> {
|
||||
let method: string = '';
|
||||
|
||||
if (request instanceof JsonRPCRequest) {
|
||||
method = 'json_rpc';
|
||||
}
|
||||
else {
|
||||
method = request.method;
|
||||
}
|
||||
|
||||
const response = await firstValueFrom<{ [key: string]: any }>(this.httpClient.post(`${this.url}/${method}`, request.toDictionary(), this.headers));
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
public async startDaemon(): Promise<void> {
|
||||
|
@ -74,7 +165,7 @@ export class DaemonService {
|
|||
|
||||
console.log("Starting daemon");
|
||||
|
||||
this.electronService.ipcRenderer.send('start-monerod', this.configFilePath);
|
||||
this.electronService.ipcRenderer.send('start-monerod', this.settings.toCommandOptions());
|
||||
|
||||
console.log("Daemon started");
|
||||
|
||||
|
@ -88,39 +179,52 @@ export class DaemonService {
|
|||
return this.daemonRunning;
|
||||
}
|
||||
|
||||
const response = await this.callJsonRpc(new EmptyRpcRequest());
|
||||
console.log(response);
|
||||
this.daemonRunning = true;
|
||||
await this.callRpc(new EmptyRpcRequest());
|
||||
}
|
||||
catch(error) {
|
||||
if (error instanceof MethodNotFoundError) {
|
||||
this.daemonRunning = true;
|
||||
return this.daemonRunning;
|
||||
}
|
||||
|
||||
console.error(error);
|
||||
this.daemonRunning = false;
|
||||
}
|
||||
|
||||
|
||||
this.daemonRunning = false;
|
||||
return this.daemonRunning;
|
||||
|
||||
}
|
||||
|
||||
public async getBlock(heightOrHash: number | string, fillPowHash: boolean = false): Promise<Block> {
|
||||
const response = await this.callRpc(new GetBlockRequest(heightOrHash, fillPowHash));
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
|
||||
return Block.parse(response.result);
|
||||
}
|
||||
|
||||
public async getBlockCount(): Promise<BlockCount> {
|
||||
const response = await this.callJsonRpc(new GetBlockCountRequest());
|
||||
const response = await this.callRpc(new GetBlockCountRequest());
|
||||
|
||||
return BlockCount.parse(response.result);
|
||||
}
|
||||
|
||||
public async getBlockHash(blockHeight: number): Promise<string> {
|
||||
const response = await this.callJsonRpc(new GetBlockHashRequest(blockHeight));
|
||||
const response = await this.callRpc(new GetBlockHashRequest(blockHeight));
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
public async getBlockTemplate(walletAddress: string, reserveSize: number) {
|
||||
const response = await this.callJsonRpc(new GetBlockTemplateRequest(walletAddress, reserveSize));
|
||||
const response = await this.callRpc(new GetBlockTemplateRequest(walletAddress, reserveSize));
|
||||
|
||||
return BlockTemplate.parse(response.result);
|
||||
}
|
||||
|
||||
public async submitBlock(... blockBlobData: string[]): Promise<void> {
|
||||
const response = await this.callJsonRpc(new SubmitBlockRequest(blockBlobData));
|
||||
const response = await this.callRpc(new SubmitBlockRequest(blockBlobData));
|
||||
|
||||
if (response.error) {
|
||||
if (!response.message) {
|
||||
|
@ -132,31 +236,31 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async generateBlocks(amountOfBlocks: number, walletAddress: string, prevBlock: string = '', startingNonce: number): Promise<GeneratedBlocks> {
|
||||
const response = await this.callJsonRpc(new GenerateBlocksRequest(amountOfBlocks, walletAddress, prevBlock, startingNonce));
|
||||
const response = await this.callRpc(new GenerateBlocksRequest(amountOfBlocks, walletAddress, prevBlock, startingNonce));
|
||||
|
||||
return GeneratedBlocks.parse(response.result);
|
||||
}
|
||||
|
||||
public async getLastBlockHeader(fillPowHash: boolean = false): Promise<BlockHeader> {
|
||||
const response = await this.callJsonRpc(new GetLastBlockHeaderRequest(fillPowHash));
|
||||
const response = await this.callRpc(new GetLastBlockHeaderRequest(fillPowHash));
|
||||
|
||||
return BlockHeader.parse(response.block_header);
|
||||
}
|
||||
|
||||
public async getBlockHeaderByHash(hash: string, fillPowHash: boolean = false): Promise<BlockHeader> {
|
||||
const response = await this.callJsonRpc(new GetBlockHeaderByHashRequest(hash, fillPowHash));
|
||||
const response = await this.callRpc(new GetBlockHeaderByHashRequest(hash, fillPowHash));
|
||||
|
||||
return BlockHeader.parse(response.block_header);
|
||||
}
|
||||
|
||||
public async getBlockHeaderByHeight(height: number, fillPowHash: boolean = false): Promise<BlockHeader> {
|
||||
const response = await this.callJsonRpc(new GetBlockHeaderByHeightRequest(height, fillPowHash));
|
||||
const response = await this.callRpc(new GetBlockHeaderByHeightRequest(height, fillPowHash));
|
||||
|
||||
return BlockHeader.parse(response.block_header);
|
||||
}
|
||||
|
||||
public async getBlockHeadersRange(startHeight: number, endHeight: number, fillPowHash: boolean = false): Promise<BlockHeader[]> {
|
||||
const response = await this.callJsonRpc(new GetBlockHeadersRangeRequest(startHeight, endHeight, fillPowHash));
|
||||
const response = await this.callRpc(new GetBlockHeadersRangeRequest(startHeight, endHeight, fillPowHash));
|
||||
const block_headers: any[] = response.block_headers;
|
||||
const result: BlockHeader[] = [];
|
||||
|
||||
|
@ -166,7 +270,7 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async getConnections(): Promise<Connection[]> {
|
||||
const response = await this.callJsonRpc(new GetConnectionsRequest());
|
||||
const response = await this.callRpc(new GetConnectionsRequest());
|
||||
const connections: any[] = response.connections;
|
||||
const result: Connection[] = [];
|
||||
|
||||
|
@ -176,19 +280,19 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async getInfo(): Promise<DaemonInfo> {
|
||||
const response = await this.callJsonRpc(new GetInfoRequest());
|
||||
const response = await this.callRpc(new GetInfoRequest());
|
||||
|
||||
return DaemonInfo.parse(response.result);
|
||||
}
|
||||
|
||||
public async hardForkInfo(): Promise<HardForkInfo> {
|
||||
const response = await this.callJsonRpc(new HardForkInfoRequest());
|
||||
const response = await this.callRpc(new HardForkInfoRequest());
|
||||
|
||||
return HardForkInfo.parse(response.result);
|
||||
}
|
||||
|
||||
public async setBans(...bans: Ban[]) {
|
||||
const response = await this.callJsonRpc(new SetBansRequest(bans));
|
||||
const response = await this.callRpc(new SetBansRequest(bans));
|
||||
|
||||
if (response.status != 'OK') {
|
||||
throw new Error(`Error code: ${response.status}`);
|
||||
|
@ -196,7 +300,7 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async getBans(): Promise<Ban[]> {
|
||||
const response = await this.callJsonRpc(new GetBansRequest());
|
||||
const response = await this.callRpc(new GetBansRequest());
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
|
@ -211,7 +315,7 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async banned(address: string): Promise<Ban> {
|
||||
const response = await this.callJsonRpc(new BannedRequest(address));
|
||||
const response = await this.callRpc(new BannedRequest(address));
|
||||
const result = response.result;
|
||||
|
||||
if (result.status != 'OK') {
|
||||
|
@ -222,43 +326,78 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async flushTxPool(... txIds: string[]): Promise<void> {
|
||||
const response = await this.callJsonRpc(new FlushTxPoolRequest(txIds));
|
||||
const response = await this.callRpc(new FlushTxPoolRequest(txIds));
|
||||
|
||||
if (response.status != 'OK') {
|
||||
throw new Error(`Error code: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getOuts(outputs: Output[], getTxId: boolean): Promise<OutKey[]> {
|
||||
const response = await this.callRpc(new GetOutsRequest(outputs, getTxId));
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
|
||||
const _outkeys: any[] | undefined = response.outs;
|
||||
const outkeys: OutKey[] = [];
|
||||
|
||||
if (_outkeys) _outkeys.forEach((outkey) => outkeys.push(OutKey.parse(outkey)));
|
||||
|
||||
return outkeys;
|
||||
}
|
||||
|
||||
public async getOutputHistogram(amounts: number[], minCount: number, maxCount: number, unlocked: boolean, recentCutoff: number): Promise<HistogramEntry[]> {
|
||||
const response = await this.callJsonRpc(new GetOutputHistogramRequest(amounts, minCount, maxCount, unlocked, recentCutoff));
|
||||
const entries: any[] = response.histogram;
|
||||
const response = await this.callRpc(new GetOutputHistogramRequest(amounts, minCount, maxCount, unlocked, recentCutoff));
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
|
||||
const entries: any[] = response.result.histogram;
|
||||
const result: HistogramEntry[] = [];
|
||||
|
||||
entries.forEach((entry: any) => result.push(HistogramEntry.parse(entry)));
|
||||
if (entries) entries.forEach((entry: any) => result.push(HistogramEntry.parse(entry)));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async getOutputDistribution(amounts: number[], cumulative: boolean, fromHeight: number, toHeight: number): Promise<OutputDistribution[]> {
|
||||
const response = await this.callRpc(new GetOutputDistributionRequest(amounts, cumulative, fromHeight, toHeight));
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
|
||||
const entries: any[] = response.result.distributions;
|
||||
const distributions: OutputDistribution[] = [];
|
||||
|
||||
if (entries) entries.forEach((entry) => distributions.push(OutputDistribution.parse(entry)));
|
||||
|
||||
return distributions;
|
||||
}
|
||||
|
||||
public async syncInfo(): Promise<SyncInfo> {
|
||||
const response = await this.callJsonRpc(new SyncInfoRequest());
|
||||
const response = await this.callRpc(new SyncInfoRequest());
|
||||
|
||||
return SyncInfo.parse(response.result);
|
||||
}
|
||||
|
||||
public async getVersion(): Promise<DaemonVersion> {
|
||||
const response = await this.callJsonRpc(new GetVersionRequest());
|
||||
const response = await this.callRpc(new GetVersionRequest());
|
||||
|
||||
return DaemonVersion.parse(response.result);
|
||||
}
|
||||
|
||||
public async getFeeEstimate(): Promise<FeeEstimate> {
|
||||
const response = await this.callJsonRpc(new GetFeeEstimateRequest());
|
||||
const response = await this.callRpc(new GetFeeEstimateRequest());
|
||||
|
||||
return FeeEstimate.parse(response.result);
|
||||
}
|
||||
|
||||
public async getAlternateChains(): Promise<Chain[]> {
|
||||
const response = await this.callJsonRpc(new GetAlternateChainsRequest());
|
||||
const response = await this.callRpc(new GetAlternateChainsRequest());
|
||||
const chains: any[] = response.result.chains ? response.result.chains : [];
|
||||
const result: Chain[] = [];
|
||||
|
||||
|
@ -267,8 +406,18 @@ export class DaemonService {
|
|||
return result;
|
||||
}
|
||||
|
||||
public async getCoinbaseTxSum(height: number, count: number): Promise<CoinbaseTxSum> {
|
||||
const response = await this.callRpc(new GetCoinbaseTxSumRequest(height, count));
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
|
||||
return CoinbaseTxSum.parse(response.result);
|
||||
}
|
||||
|
||||
public async relayTx(... txIds: string[]): Promise<void> {
|
||||
const response = await this.callJsonRpc(new RelayTxRequest(txIds));
|
||||
const response = await this.callRpc(new RelayTxRequest(txIds));
|
||||
|
||||
if (response.result.status != 'OK') {
|
||||
throw new Error(`Error code: ${response.result.status}`);
|
||||
|
@ -276,25 +425,25 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async getTxPoolBacklog(): Promise<TxBacklogEntry[]> {
|
||||
const response = await this.callJsonRpc(new GetTxPoolBacklogRequest());
|
||||
const response = await this.callRpc(new GetTxPoolBacklogRequest());
|
||||
|
||||
return TxBacklogEntry.fromBinary(response.backlog);
|
||||
}
|
||||
|
||||
public async pruneBlockchain(check: boolean = false): Promise<BlockchainPruneInfo> {
|
||||
const response = await this.callJsonRpc(new PruneBlockchainRequest(check));
|
||||
const response = await this.callRpc(new PruneBlockchainRequest(check));
|
||||
|
||||
return BlockchainPruneInfo.parse(response.result);
|
||||
}
|
||||
|
||||
public async calculatePoWHash(majorVersion: number, height: number, blockBlob: string, seedHash: string): Promise<string> {
|
||||
const response = await this.callJsonRpc(new CalculatePoWHashRequest(majorVersion, height, blockBlob, seedHash));
|
||||
const response = await this.callRpc(new CalculatePoWHashRequest(majorVersion, height, blockBlob, seedHash));
|
||||
|
||||
return response.result;
|
||||
}
|
||||
|
||||
public async flushCache(badTxs: boolean = false, badBlocks: boolean = false): Promise<void> {
|
||||
const response = await this.callJsonRpc(new FlushCacheRequest(badTxs, badBlocks));
|
||||
const response = await this.callRpc(new FlushCacheRequest(badTxs, badBlocks));
|
||||
|
||||
if(response.result.status != 'OK') {
|
||||
throw new Error(`Error code: ${response.result.status}`);
|
||||
|
@ -302,25 +451,151 @@ export class DaemonService {
|
|||
}
|
||||
|
||||
public async getMinerData(): Promise<MinerData> {
|
||||
const response = await this.callJsonRpc(new GetMinerDataRequest());
|
||||
|
||||
if (response.error) {
|
||||
this.raiseRpcError(response.error);
|
||||
}
|
||||
const response = await this.callRpc(new GetMinerDataRequest());
|
||||
|
||||
return MinerData.parse(response.result);
|
||||
}
|
||||
|
||||
private raiseRpcError(error: { code: number, message: string }): void {
|
||||
public async AddAuxPoW(blockTemplateBlob: string, auxPoW: AuxPoW[]): Promise<AddedAuxPow> {
|
||||
const response = await this.callRpc(new AddAuxPoWRequest(blockTemplateBlob, auxPoW));
|
||||
|
||||
if (error.code == -9) {
|
||||
throw new CoreIsBusyError();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Error(error.message);
|
||||
}
|
||||
return AddedAuxPow.parse(response.result);
|
||||
}
|
||||
|
||||
public async setBootstrapDaemon(address: string, username: string = '', password: string = '', proxy: string = ''): Promise<void> {
|
||||
const response = await this.callRpc(new SetBootstrapDaemonRequest(address, username, password, proxy));
|
||||
|
||||
if (typeof response.status == 'string' && response.status != 'OK') {
|
||||
throw new Error(`Could not set bootstrap daemon: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async saveBc(): Promise<void> {
|
||||
const response = await this.callRpc(new SaveBcRequest());
|
||||
|
||||
if (typeof response.status == 'string' && response.status != 'OK') {
|
||||
throw new Error(`Could not save blockchain: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async getAltBlockHashes(): Promise<string[]> {
|
||||
const response = await this.callRpc(new GetAltBlockHashesRequest());
|
||||
|
||||
return response.blks_hashes;
|
||||
}
|
||||
|
||||
public async isKeyImageSpent(...keyImages: string[]): Promise<number[]> {
|
||||
const response = await this.callRpc(new IsKeyImageSpentRequest(keyImages));
|
||||
|
||||
return response.spent_status;
|
||||
}
|
||||
|
||||
public async sendRawTransaction(txAsHex: string, doNotRelay: boolean = false): Promise<TxInfo> {
|
||||
const response = await this.callRpc(new SendRawTransactionRequest(txAsHex, doNotRelay));
|
||||
|
||||
return TxInfo.parse(response);
|
||||
}
|
||||
|
||||
public async startMining(doBackgroundMining: boolean, ignoreBattery: boolean, minerAddress: string, threadsCount: number): Promise<void> {
|
||||
const response = await this.callRpc(new StartMiningRequest(doBackgroundMining, ignoreBattery, minerAddress, threadsCount));
|
||||
|
||||
if (typeof response.status == 'string' && response.status != 'OK') {
|
||||
throw new Error(`Could not start mining: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async stopMining(): Promise<void> {
|
||||
const response = await this.callRpc(new StopMiningRequest());
|
||||
|
||||
if (typeof response.status == 'string' && response.status != 'OK') {
|
||||
throw new Error(`Could not stop mining: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async miningStatus(): Promise<MiningStatus> {
|
||||
const response = await this.callRpc(new MiningStatusRequest());
|
||||
|
||||
return MiningStatus.parse(response);
|
||||
}
|
||||
|
||||
public async stopDaemon(): Promise<void> {
|
||||
const response = await this.callRpc(new StopDaemonRequest());
|
||||
|
||||
if (typeof response.status == 'string' && response.status != 'OK') {
|
||||
throw new Error(`Could not stop daemon: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
public async setLimit(limitDown: number, limitUp: number): Promise<{ limitDown: number, limitUp: number }> {
|
||||
const response = await this.callRpc(new SetLimitRequest(limitDown, limitUp));
|
||||
|
||||
return {
|
||||
limitDown: response.limit_down,
|
||||
limitUp: response.limit_up
|
||||
};
|
||||
}
|
||||
|
||||
public async inPeers(inPeers: number): Promise<number> {
|
||||
const response = await this.callRpc(new InPeersRequest(inPeers));
|
||||
|
||||
return response.in_peers;
|
||||
}
|
||||
|
||||
public async outPeers(outPeers: number): Promise<number> {
|
||||
const response = await this.callRpc(new OutPeersRequest(outPeers));
|
||||
|
||||
return response.out_peers;
|
||||
}
|
||||
|
||||
public async getNetStats(): Promise<NetStats> {
|
||||
const response = await this.callRpc(new GetNetStatsRequest());
|
||||
|
||||
return NetStats.parse(response);
|
||||
}
|
||||
|
||||
public async getPublicNodes(whites: boolean = true, grays: boolean = false, includeBlocked: boolean = false): Promise<PublicNode[]> {
|
||||
const response = await this.callRpc(new GetPublicNodesRequest(whites, grays, includeBlocked));
|
||||
|
||||
const _whites: any[] | undefined = response.whites;
|
||||
const _grays: any[] | undefined = response.grays;
|
||||
const nodes: PublicNode[] = [];
|
||||
|
||||
if (_whites) _whites.forEach((white) => nodes.push(PublicNode.parse(white, 'white')));
|
||||
if (_grays) _grays.forEach((gray) => nodes.push(PublicNode.parse(gray, 'gray')));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
public async getTransactionPoolHashes(): Promise<string[]> {
|
||||
const response = await this.callRpc(new GetTransactionPoolHashesRequest());
|
||||
|
||||
return response.tx_hashes;
|
||||
}
|
||||
|
||||
public async getTransactionPoolHashesBinary(): Promise<string> {
|
||||
const response = await this.callRpc(new GetTransactionPoolHashesBinaryRequest());
|
||||
|
||||
return response.tx_hashes;
|
||||
}
|
||||
|
||||
public async popBlocks(nBlocks: number): Promise<number> {
|
||||
const response = await this.callRpc(new PopBlocksRequest(nBlocks));
|
||||
|
||||
return response.height;
|
||||
}
|
||||
|
||||
public async update(command: 'check' | 'download', path: string = ''): Promise<UpdateInfo> {
|
||||
const response = await this.callRpc(new UpdateRequest(command, path));
|
||||
|
||||
return UpdateInfo.parse(response);
|
||||
}
|
||||
|
||||
public async checkUpdate(): Promise<UpdateInfo> {
|
||||
return await this.update('check');
|
||||
}
|
||||
|
||||
public async downloadUpdate(path: string = ''): Promise<UpdateInfo> {
|
||||
return await this.update('download', path);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab" tabindex="0">
|
||||
<div class="row d-flex">
|
||||
|
||||
<div *ngIf="!daemonRunning" class="h-100 p-5 text-bg-dark rounded-3 m-4">
|
||||
<div *ngIf="!daemonRunning" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
|
||||
<h2><i class="bi bi-exclamation-diamond"></i> Daemon not running</h2>
|
||||
<p>Start monero daemon</p>
|
||||
<button *ngIf="!startingDaemon" class="btn btn-outline-light" type="button" (click)="startDaemon()"><i class="bi bi-play-fill"></i> Start</button>
|
||||
|
|
|
@ -90,7 +90,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
console.log('DetailComponent AFTER VIEW INIT');
|
||||
this.navbarService.setNavbarLinks(this.navbarLinks);
|
||||
|
||||
this.loadInterval = setInterval(() => {
|
||||
setTimeout(() => {
|
||||
this.ngZone.run(() => {
|
||||
if (this.isLoading) {
|
||||
return;
|
||||
|
@ -103,7 +103,7 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||
this.load();
|
||||
|
||||
});
|
||||
}, 5000);
|
||||
}, 500);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
|
|
@ -50,7 +50,8 @@ export class MiningComponent implements AfterViewInit {
|
|||
new NavbarLink('pills-block-template-tab', '#pills-block-template', 'block-template', false, 'Block Template'),
|
||||
new NavbarLink('pills-generate-blocks-tab', '#pills-generate-blocks', 'generate-blocks', false, 'Generate Blocks'),
|
||||
new NavbarLink('pills-submit-block-tab', '#pills-submit-block', 'submit-block', false, 'Submit Block'),
|
||||
new NavbarLink('pills-calc-pow-tab', '#pills-calc-pow', 'calc-pow', false, 'Calculate PoW Hash')
|
||||
new NavbarLink('pills-calc-pow-tab', '#pills-calc-pow', 'calc-pow', false, 'Calculate PoW Hash'),
|
||||
new NavbarLink('pills-add-aux-pow-tab', '#pills-add-aux-pow', 'add-aux-pow', false, 'Add Aux PoW')
|
||||
];
|
||||
|
||||
this.router.events.subscribe((event) => {
|
||||
|
|
|
@ -7,73 +7,73 @@
|
|||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="offline" class="form-check-label">Offline</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="offline">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="offline" (change)="OnOfflineChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Do not listen for peers, nor connect to any</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="public-node" class="form-check-label">Public node</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="public-node">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="public-node" (change)="OnPublicNodeChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow other users to use the node as a remote (restricted RPC mode, view-only commands) and advertise it over P2P</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="restricted-rpc" class="form-check-label">Restricted RPC</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="restricted-rpc">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="restricted-rpc" (change)="OnRestrictedRPCChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Restrict RPC to view-only commands and do not return privacy sensitive data in RPC calls</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="confirm-external-bind" class="form-check-label">Confirm external bind</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="confirm-external-bind">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="confirm-external-bind" (change)="OnConfirmExternalBindChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Confirm Bind IP is not a loopback (local) IP</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="rpc-ignore-ipv4" class="form-check-label">Ignore IPv4</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-ignore-ipv4">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-ignore-ipv4" (change)="OnIgnoreIPv4Change()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Ignore unsuccessful IPv4 bind for RPC</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="disable-rpc-ban" class="form-check-label">Disable ban</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="disable-rpc-ban">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="disable-rpc-ban" (change)="OnDisableRpcBanChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Do not ban hosts on RPC errors</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rpc-bind-ip" class="form-label">Bind IP</label>
|
||||
<input type="text" class="form-control" id="rpc-bind-ip" placeholder="127.0.0.1">
|
||||
<input type="text" class="form-control" id="rpc-bind-ip" placeholder="127.0.0.1" [(ngModel)]="currentSettings.rpcBindIp" [ngModelOptions]="{standalone: true}" >
|
||||
<small class="text-body-secondary">Specify IP to bind RPC server</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rpc-bind-port" class="form-label">Bind port</label>
|
||||
<input type="number" class="form-control" id="rpc-bind-port" placeholder="18081">
|
||||
<input type="number" class="form-control" id="rpc-bind-port" placeholder="18081" [(ngModel)]="currentSettings.rpcBindPort" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">18081 for mainnet, 28081 for testnet, 38081 for stagenet</small>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<label for="rpc-login-user" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="rpc-login-user" placeholder="" value="">
|
||||
<input type="text" class="form-control" id="rpc-login-user" placeholder="" [(ngModel)]="rpcLoginUser" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify username required for RPC server</small>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<label for="rpc-login-password" class="form-label">Password</label>
|
||||
<input type="password" class="form-control" id="rpc-login-password" placeholder="" value="">
|
||||
<input type="password" class="form-control" id="rpc-login-password" placeholder="" [(ngModel)]="rpcLoginPassword" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify password required for RPC server</small>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<label for="rpc-access-control-origins" class="form-label">Access control origins</label>
|
||||
<input type="text" class="form-control" id="rpc-access-control-origins" placeholder="" value="">
|
||||
<input type="text" class="form-control" id="rpc-access-control-origins" placeholder="" [(ngModel)]="currentSettings.rpcAccessControlOrigins" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify a comma separated list of origins to allow cross origin resource sharing</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -85,14 +85,14 @@
|
|||
|
||||
<div class="form-check form-switch col-md-12">
|
||||
<label for="rpc-use-ipv6" class="form-check-label">Enabled</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-use-ipv6">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-use-ipv6" [(ngModel)]="currentSettings.rpcUseIpv6" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow IPv6 for RPC</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rpc-bind-ipv6-address" class="form-label">Bind IPv6 address</label>
|
||||
<input type="text" class="form-control" id="rpc-bind-ipv6-address" placeholder="::1">
|
||||
<input type="text" class="form-control" id="rpc-bind-ipv6-address" placeholder="::1" [(ngModel)]="currentSettings.rpcBindIpv6Address" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify IPv6 address to bind RPC server</small>
|
||||
</div>
|
||||
|
||||
|
@ -111,26 +111,26 @@
|
|||
|
||||
<div class="form-check form-switch col-md-12">
|
||||
<label for="enable-zmq" class="form-check-label">Enabled</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="enable-zmq">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="enable-zmq" [checked]="!currentSettings.noZmq" (change)="OnNoZmqChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Enable ZMQ RPC Server</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="zmq-rpc-bind-ip" class="form-label">Bind IP</label>
|
||||
<input type="text" class="form-control" id="zmq-rpc-bind-ip" placeholder="127.0.0.1">
|
||||
<input type="text" class="form-control" id="zmq-rpc-bind-ip" placeholder="127.0.0.1" [(ngModel)]="currentSettings.zmqRpcBindIp" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">IP for ZMQ RPC Server to listen on</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="zmq-rpc-bind-port" class="form-label">Bind port</label>
|
||||
<input type="number" class="form-control" id="zmq-rpc-bind-port" placeholder="18082">
|
||||
<input type="number" class="form-control" id="zmq-rpc-bind-port" placeholder="18082" [(ngModel)]="currentSettings.zmqRpcBindPort" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">18082 for mainnet, 28082 for testnet, 38082 for stagenet</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="zmq-pub" class="form-label">ZMQ Pub</label>
|
||||
<input type="text" class="form-control" id="zmq-pub" placeholder="tcp://ip:port or ipc://path">
|
||||
<input type="text" class="form-control" id="zmq-pub" placeholder="tcp://ip:port or ipc://path" [(ngModel)]="currentSettings.zmqPub" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Address for ZMQ Pub</small>
|
||||
</div>
|
||||
|
||||
|
@ -143,25 +143,25 @@
|
|||
|
||||
<div class="col-md-12">
|
||||
<label for="rpc-payment-address" class="form-label">Address</label>
|
||||
<input type="text" class="form-control" id="rpc-payment-address">
|
||||
<input type="text" class="form-control" id="rpc-payment-address" [(ngModel)]="currentSettings.rpcPaymentAddress" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Restrict RPC to clients sending micropayment to this address</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rpc-payment-difficulty" class="form-label">Difficulty</label>
|
||||
<input type="number" class="form-control" id="rpc-payment-difficulty" placeholder="1000">
|
||||
<input type="number" class="form-control" id="rpc-payment-difficulty" placeholder="1000" [(ngModel)]="currentSettings.rpcPaymentDifficuly" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Restrict RPC to clients sending micropayment at this difficulty</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="rpc-payment-credits" class="form-label">Credits</label>
|
||||
<input type="number" class="form-control" id="rpc-payment-credits" placeholder="100">
|
||||
<input type="number" class="form-control" id="rpc-payment-credits" placeholder="100" [(ngModel)]="currentSettings.rpcPaymentCredits" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Restrict RPC to clients sending micropayment, yelds that many credits per payment</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="rpc-payment-allow-free-loopback" class="form-check-label">Allow free loopback</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-payment-allow-free-loopback">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-payment-allow-free-loopback" [(ngModel)]="currentSettings.rpcPaymentAllowFreeLoopback" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow free access from the loopback address (ie, the local host)</small>
|
||||
</div>
|
||||
|
@ -175,43 +175,43 @@
|
|||
|
||||
<div class="col-md-4">
|
||||
<label for="rpc-ssl" class="form-label">SSL Mode</label>
|
||||
<select class="form-select" id="rpc-ssl">
|
||||
<option value="autodetect">Autodetect</option>
|
||||
<option value="enabled">Enabled</option>
|
||||
<option value="disabled">Disabled</option>
|
||||
<select class="form-select" id="rpc-ssl" [(ngModel)]="currentSettings.rpcSsl" [ngModelOptions]="{standalone: true}">
|
||||
<option [ngValue]="'autodetect'">Autodetect</option>
|
||||
<option [ngValue]="'enabled'">Enabled</option>
|
||||
<option [ngValue]="'disabled'">Disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-check form-switch col-md-4">
|
||||
<label for="rpc-ssl-allow-chained" class="form-check-label">Allow chained</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-ssl-allow-chained">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-ssl-allow-chained" [(ngModel)]="currentSettings.rpcSslAllowChained" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow user chain certificates</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-4">
|
||||
<label for="rpc-ssl-allow-any-cert" class="form-check-label">Allow any cert</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-ssl-allow-any-cert">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="rpc-ssl-allow-any-cert" [(ngModel)]="currentSettings.rpcSslAllowAnyCert" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow any peer certificate</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="rpc-ssl-private-key" class="form-label">Private key</label>
|
||||
<input type="file" class="form-control" id="rpc-ssl-private-key">
|
||||
<input type="file" class="form-control" id="rpc-ssl-private-key" [(ngModel)]="currentSettings.rpcSslPrivateKey" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Path to a PEM format private key</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="rpc-ssl-certificate" class="form-label">Certificate</label>
|
||||
<input type="file" class="form-control" id="rpc-ssl-certificate">
|
||||
<input type="file" class="form-control" id="rpc-ssl-certificate" [(ngModel)]="currentSettings.rpcSslCertificate" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Path to a PEM format certificate</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="rpc-ssl-ca-certificates" class="form-label">CA Certificates</label>
|
||||
<input type="file" class="form-control" id="rpc-ssl-ca-certificates">
|
||||
<input type="file" class="form-control" id="rpc-ssl-ca-certificates" [(ngModel)]="currentSettings.rpcSslCACertificates" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Path to file containing concatenated PEM format certificate(s) to replace system CA(s)</small>
|
||||
</div>
|
||||
|
||||
|
@ -229,33 +229,33 @@
|
|||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="allow-local-ip" class="form-check-label">Allow local IP</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="allow-local-ip">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="allow-local-ip" [(ngModel)]="currentSettings.allowLocalIp" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow local ip add to peer list, mostly in debug process</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="p2p-ignore-ipv4" class="form-check-label">Ignore IPv4</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="p2p-ignore-ipv4">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="p2p-ignore-ipv4" [(ngModel)]="currentSettings.p2pIgnoreIpv4">
|
||||
<br>
|
||||
<small class="text-body-secondary">Ignore unsuccessful IPv4 bind for P2P</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="p2p-bind-ip" class="form-label">Bind IP</label>
|
||||
<input type="text" class="form-control" id="p2p-bind-ip" placeholder="0.0.0.0">
|
||||
<input type="text" class="form-control" id="p2p-bind-ip" placeholder="0.0.0.0" [(ngModel)]="currentSettings.p2pBindIp" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Interface for p2p network protocol</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="p2p-bind-port" class="form-label">Bind port</label>
|
||||
<input type="number" class="form-control" id="p2p-bind-port" placeholder="18080">
|
||||
<input type="number" class="form-control" id="p2p-bind-port" placeholder="18080" [(ngModel)]="currentSettings.p2pBindPort" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">18080 for mainnet, 28080 for testnet, 38080 for stagenet</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="p2p-external-port" class="form-label">External port</label>
|
||||
<input type="number" class="form-control" id="p2p-external-port" placeholder="18080">
|
||||
<input type="number" class="form-control" id="p2p-external-port" placeholder="18080" [(ngModel)]="currentSettings.p2pExternalPort" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">External port for p2p network protocol (if port forwarding used with NAT)</small>
|
||||
</div>
|
||||
|
||||
|
@ -266,13 +266,13 @@
|
|||
|
||||
<div class="col-md-6">
|
||||
<label for="p2p-bind-ipv6-address" class="form-label">Bind IPv6 address</label>
|
||||
<input type="text" class="form-control" id="p2p-bind-ipv6-address" placeholder="::1">
|
||||
<input type="text" class="form-control" id="p2p-bind-ipv6-address" placeholder="::1" [(ngModel)]="currentSettings.p2pBindIpv6Address" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify IPv6 address to bind RPC server</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="p2p-bind-port-ipv6" class="form-label">Bind IPv6 port</label>
|
||||
<input type="number" class="form-control" id="p2p-bind-port-ipv6" placeholder="18080">
|
||||
<input type="number" class="form-control" id="p2p-bind-port-ipv6" placeholder="18080" [(ngModel)]="currentSettings.p2pBindPortIpv6" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">18080 for mainnet, 28080 for testnet, 38080 for stagenet</small>
|
||||
</div>
|
||||
|
||||
|
@ -293,13 +293,13 @@
|
|||
|
||||
<div class="col-12">
|
||||
<label for="address" class="form-label">Address</label>
|
||||
<input type="text" class="form-control" id="address" placeholder="Use 'auto' to enable automatic discovering and switching">
|
||||
<input type="text" class="form-control" id="address" placeholder="Use 'auto' to enable automatic discovering and switching" [(ngModel)]="currentSettings.bootstrapDaemonAddress" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">URL of a bootstrap remote daemon that the connected wallets can use while this daemon is still not fully synced.</small>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-6">
|
||||
<label for="firstName" class="form-label">Username</label>
|
||||
<input type="text" class="form-control" id="firstName" placeholder="" value="">
|
||||
<input type="text" class="form-control" id="firstName" placeholder="">
|
||||
<small class="text-body-secondary">Specify username for the bootstrap daemon login</small>
|
||||
</div>
|
||||
|
||||
|
@ -312,7 +312,7 @@
|
|||
|
||||
<div class="col-12">
|
||||
<label for="bootstrap-daemon-proxy" class="form-label">Proxy</label>
|
||||
<input type="text" class="form-control" id="bootstrap-daemon-proxy" placeholder="ip:port">
|
||||
<input type="text" class="form-control" id="bootstrap-daemon-proxy" placeholder="ip:port" [(ngModel)]="currentSettings.bootstrapDaemonProxy" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Socks proxy to use for bootstrap daemon connection</small>
|
||||
</div>
|
||||
|
||||
|
@ -325,59 +325,59 @@
|
|||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="sync-enabled" class="form-check-label">Enabled</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="sync-enabled" checked>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="sync-enabled" [checked]="!currentSettings.noSync" (change)="OnSyncEnableChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Synchronize the blockchain with other peers</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="prune-blockchain" class="form-check-label">Prune Blockchain</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="prune-blockchain">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="prune-blockchain" [(ngModel)]="currentSettings.pruneBlockchain" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Reduce blockchain disk usage</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="sync-pruned-blocks" class="form-check-label">Sync pruned blocks</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="sync-pruned-blocks">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="sync-pruned-blocks" [(ngModel)]="currentSettings.syncPrunedBlocks" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Allow syncing from nodes with only pruned blocks</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="fast-block-sync" class="form-check-label">Fast block sync</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="fast-block-sync">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="fast-block-sync" [(ngModel)]="currentSettings.fastBlockSync" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Sync up most of the way by using embedded, known block hashes</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="relay-fluffly-blocks" class="form-check-label">Relay fluffy blocks</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="relay-fluffly-blocks" checked>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="relay-fluffly-blocks" [checked]="!currentSettings.noFluffyBlocks" (change)="OnRelayFlufflyBlocksChange()">
|
||||
<br>
|
||||
<small class="text-body-secondary">Relay blocks as fluffy blocks</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="keep-alt-blocks" class="form-check-label">Keep alternative blocks</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="keep-alt-blocks">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="keep-alt-blocks" [(ngModel)]="currentSettings.keepAltBlocks" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Keep alternative blocks on restart</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="block-sync-size" class="form-label">Block sync size</label>
|
||||
<input type="number" class="form-control" id="block-sync-size" placeholder="" value="0">
|
||||
<input type="number" class="form-control" id="block-sync-size" placeholder="" [(ngModel)]="currentSettings.blockSyncSize" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="block-download-max-size" class="form-label">Block download max size</label>
|
||||
<input type="number" class="form-control" id="block-download-max-size" placeholder="" value="0">
|
||||
<input type="number" class="form-control" id="block-download-max-size" placeholder="" [(ngModel)]="currentSettings.blockDownloadMaxSize" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="db-sync-mode" class="form-label">Database sync mode</label>
|
||||
<input type="text" class="form-control" id="db-sync-mode" placeholder="fast:async:250000000bytes">
|
||||
<input type="text" class="form-control" id="db-sync-mode" placeholder="fast:async:250000000bytes" [(ngModel)]="currentSettings.dbSyncMode" [ngModelOptions]="{standalone: true}">
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
|
@ -386,59 +386,20 @@
|
|||
|
||||
<div class="my-3">
|
||||
<div class="form-check">
|
||||
<input id="credit" name="paymentMethod" type="radio" class="form-check-input" checked="" required="">
|
||||
<input id="credit" name="paymentMethod" type="radio" class="form-check-input">
|
||||
<label class="form-check-label" for="credit">mainnet</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="debit" name="paymentMethod" type="radio" class="form-check-input" required="">
|
||||
<input id="debit" name="paymentMethod" type="radio" class="form-check-input" [(ngModel)]="currentSettings.testnet" [ngModelOptions]="{standalone: true}">
|
||||
<label class="form-check-label" for="debit">testnet</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input id="paypal" name="paymentMethod" type="radio" class="form-check-input" required="">
|
||||
<input id="paypal" name="paymentMethod" type="radio" class="form-check-input" [(ngModel)]="currentSettings.stagenet" [ngModelOptions]="{standalone: true}">
|
||||
<label class="form-check-label" for="paypal">stagenet</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="row gy-3">
|
||||
<div class="col-md-6">
|
||||
<label for="cc-name" class="form-label">Name on card</label>
|
||||
<input type="text" class="form-control" id="cc-name" placeholder="" required="">
|
||||
<small class="text-body-secondary">Full name as displayed on card</small>
|
||||
<div class="invalid-feedback">
|
||||
Name on card is required
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="cc-number" class="form-label">Credit card number</label>
|
||||
<input type="text" class="form-control" id="cc-number" placeholder="" required="">
|
||||
<div class="invalid-feedback">
|
||||
Credit card number is required
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="cc-expiration" class="form-label">Expiration</label>
|
||||
<input type="text" class="form-control" id="cc-expiration" placeholder="" required="">
|
||||
<div class="invalid-feedback">
|
||||
Expiration date required
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="cc-cvv" class="form-label">CVV</label>
|
||||
<input type="text" class="form-control" id="cc-cvv" placeholder="" required="">
|
||||
<div class="invalid-feedback">
|
||||
Security code required
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<hr class="my-4">
|
||||
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -454,33 +415,33 @@
|
|||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="bg-mining-enable" class="form-check-label">Enabled</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="bg-mining-enable">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="bg-mining-enable" [(ngModel)]="currentSettings.bgMiningEnable" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Enable background mining</small>
|
||||
</div>
|
||||
|
||||
<div class="form-check form-switch col-md-6">
|
||||
<label for="bg-mining-ignore-battery" class="form-check-label">Ignore battery</label>
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="bg-mining-ignore-battery">
|
||||
<input class="form-control form-check-input" type="checkbox" role="switch" id="bg-mining-ignore-battery" [(ngModel)]="currentSettings.bgMiningIgnoreBattery" [ngModelOptions]="{standalone: true}">
|
||||
<br>
|
||||
<small class="text-body-secondary">Reduce blockchain disk usage</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="bg-mining-min-idle-interval" class="form-label">Minimum idle interval</label>
|
||||
<input type="number" class="form-control" id="bg-mining-min-idle-interval" placeholder="" value="">
|
||||
<input type="number" class="form-control" id="bg-mining-min-idle-interval" placeholder="" [(ngModel)]="currentSettings.bgMiningMinIdleInterval" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify min lookback interval in seconds for determining idle state</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="bg-mining-idle-threshold" class="form-label">Idle threshold</label>
|
||||
<input type="number" class="form-control" id="bg-mining-idle-threshold" placeholder="" value="">
|
||||
<input type="number" class="form-control" id="bg-mining-idle-threshold" placeholder="" [(ngModel)]="currentSettings.bgMiningIdleThreshold" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify minimum avg idle percentage over lookback interval</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="bg-mining-miner-target" class="form-label">Miner target</label>
|
||||
<input type="number" class="form-control" id="bg-mining-miner-target" placeholder="">
|
||||
<input type="number" class="form-control" id="bg-mining-miner-target" placeholder="" [(ngModel)]="currentSettings.bgMiningMinerTarget" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify maximum percentage cpu use by miners</small>
|
||||
</div>
|
||||
|
||||
|
@ -501,24 +462,24 @@
|
|||
|
||||
<div class="col-md-4">
|
||||
<label for="log-level" class="form-label">Log level</label>
|
||||
<select class="form-select" id="log-level">
|
||||
<option value="0">Info</option>
|
||||
<option value="1">Warning</option>
|
||||
<option value="2">Debug</option>
|
||||
<option value="3">Error</option>
|
||||
<option value="4">Trace</option>
|
||||
<select class="form-select" id="log-level" [(ngModel)]="currentSettings.logLevel" [ngModelOptions]="{standalone: true}">
|
||||
<option [ngValue]="0">Info</option>
|
||||
<option [ngValue]="1">Warning</option>
|
||||
<option [ngValue]="2">Debug</option>
|
||||
<option [ngValue]="3">Error</option>
|
||||
<option [ngValue]="4">Trace</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="max-log-file-size" class="form-label">Max log file size</label>
|
||||
<input type="number" class="form-control" id="max-log-file-size" placeholder="104850000" value="">
|
||||
<input type="number" class="form-control" id="max-log-file-size" placeholder="104850000" [(ngModel)]="currentSettings.maxLogFileSize" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify maximum log file size [B]</small>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label for="max-log-files" class="form-label">Max log files</label>
|
||||
<input type="number" class="form-control" id="max-log-files" placeholder="50" value="">
|
||||
<input type="number" class="form-control" id="max-log-files" placeholder="50" [(ngModel)]="currentSettings.maxLogFiles" [ngModelOptions]="{standalone: true}">
|
||||
<small class="text-body-secondary">Specify maximum number of rotated log files to be saved (no limit by setting to 0)</small>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -530,6 +491,6 @@
|
|||
|
||||
<hr class="my-4">
|
||||
|
||||
<button class="w-50 btn btn-primary btn-lg" type="submit">Save</button>
|
||||
<button class="w-50 btn btn-primary btn-lg" type="submit" [disabled]="!modified" (click)="OnSave()">Save</button>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -2,17 +2,27 @@ import { AfterViewInit, Component } from '@angular/core';
|
|||
import { NavbarService } from '../../shared/components/navbar/navbar.service';
|
||||
import { NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
|
||||
import { DaemonSettings } from '../../../common/DaemonSettings';
|
||||
import { FormsModule, NgModel } from '@angular/forms';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrl: './settings.component.scss'
|
||||
styleUrl: './settings.component.scss',
|
||||
imports: [FormsModule],
|
||||
standalone: true
|
||||
})
|
||||
export class SettingsComponent implements AfterViewInit {
|
||||
|
||||
private readonly navbarLinks: NavbarLink[];
|
||||
private originalSettings: DaemonSettings;
|
||||
public currentSettings: DaemonSettings;
|
||||
|
||||
public rpcLoginUser: string;
|
||||
public rpcLoginPassword: string;
|
||||
|
||||
constructor(private router: Router, private navbarService: NavbarService) {
|
||||
|
||||
|
||||
this.navbarLinks = [
|
||||
new NavbarLink('pills-rpc-tab', '#pills-rpc', 'pills-rpc', true, 'RPC'),
|
||||
|
@ -21,6 +31,18 @@ export class SettingsComponent implements AfterViewInit {
|
|||
new NavbarLink('pills-mining-tab', '#pills-mining', 'pills-mining', false, 'Mining'),
|
||||
new NavbarLink('pills-logs-tab', '#pills-logs', 'pills-logs', false, 'Logs')
|
||||
];
|
||||
|
||||
this.originalSettings = new DaemonSettings();
|
||||
this.currentSettings = this.originalSettings.clone();
|
||||
const loginArgs = this.currentSettings.rpcLogin.split(":");
|
||||
if (loginArgs.length == 2) {
|
||||
this.rpcLoginPassword = loginArgs[0];
|
||||
this.rpcLoginUser = loginArgs[1];
|
||||
}
|
||||
else {
|
||||
this.rpcLoginUser = '';
|
||||
this.rpcLoginPassword = '';
|
||||
}
|
||||
|
||||
this.router.events.subscribe((event) => {
|
||||
if (event instanceof NavigationEnd) {
|
||||
|
@ -30,13 +52,66 @@ export class SettingsComponent implements AfterViewInit {
|
|||
});
|
||||
}
|
||||
|
||||
public get modified(): boolean {
|
||||
if (!this.currentSettings.equals(this.originalSettings)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.navbarService.setNavbarLinks(this.navbarLinks);
|
||||
}
|
||||
|
||||
public OnOfflineChange() {
|
||||
this.currentSettings.offline = !this.currentSettings.offline;
|
||||
}
|
||||
|
||||
public OnPublicNodeChange() {
|
||||
this.currentSettings.publicNode = !this.currentSettings.publicNode;
|
||||
}
|
||||
|
||||
public OnRestrictedRPCChange() {
|
||||
this.currentSettings.restrictedRpc = !this.currentSettings.restrictedRpc;
|
||||
}
|
||||
|
||||
public OnConfirmExternalBindChange() {
|
||||
this.currentSettings.confirmExternalBind = !this.currentSettings.confirmExternalBind;
|
||||
}
|
||||
|
||||
public OnIgnoreIPv4Change() {
|
||||
this.currentSettings.rpcIgnoreIpv4 = !this.currentSettings.rpcIgnoreIpv4;
|
||||
}
|
||||
|
||||
public OnDisableRpcBanChange() {
|
||||
this.currentSettings.disableRpcBan = !this.currentSettings.disableRpcBan;
|
||||
}
|
||||
|
||||
public OnRpcUseIPv6Change(): void {
|
||||
this.currentSettings.rpcUseIpv6 = !this.currentSettings.rpcUseIpv6;
|
||||
}
|
||||
|
||||
public OnNoZmqChange(): void {
|
||||
this.currentSettings.noZmq = !this.currentSettings.noZmq;
|
||||
}
|
||||
|
||||
public OnSyncEnableChange(): void {
|
||||
this.currentSettings.noSync != this.currentSettings.noSync;
|
||||
}
|
||||
|
||||
public OnRelayFlufflyBlocksChange(): void {
|
||||
this.currentSettings.noFluffyBlocks = !this.currentSettings.noFluffyBlocks;
|
||||
}
|
||||
|
||||
private onNavigationEnd(): void {
|
||||
|
||||
}
|
||||
|
||||
public OnSave(): void {
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* --log-file arg (=/home/sidney/.bitmonero/bitmonero.log, /home/sidney/.bitmonero/testnet/bitmonero.log if 'testnet', /home/sidney/.bitmonero/stagenet/bitmonero.log if 'stagenet')
|
||||
|
|
|
@ -2,12 +2,16 @@ import { NgModule } from '@angular/core';
|
|||
import { CommonModule } from '@angular/common';
|
||||
|
||||
import { SettingsRoutingModule } from './settings-routing.module';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
SharedModule,
|
||||
SettingsRoutingModule
|
||||
]
|
||||
})
|
||||
|
|
|
@ -1 +1,22 @@
|
|||
<p>transactions works!</p>
|
||||
<div class="tab-pane fade show active" 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">Relay a list of transaction IDs</h4>
|
||||
<form class="needs-validation" novalidate="">
|
||||
<div class="row g-3">
|
||||
|
||||
<div class="col-12">
|
||||
<label for="tx_ids" class="form-label">Tx Ids</label>
|
||||
<textarea type="text" class="form-control" id="tx_ids" placeholder="" rows="15" cols="15"></textarea>
|
||||
<small class="text-body-secondary">List of transaction IDs to relay</small>
|
||||
</div>
|
||||
|
||||
<hr class="my-4">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<button class="w-100 btn btn-primary btn-lg" type="button" [disabled]="!canRelay" (click)="onRelay()">Relay Tx</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -11,6 +11,8 @@ import { NavbarLink } from '../../shared/components/navbar/navbar.model';
|
|||
export class TransactionsComponent implements AfterViewInit {
|
||||
private readonly navbarLinks: NavbarLink[];
|
||||
|
||||
public canRelay: boolean;
|
||||
|
||||
constructor(private daemonService: DaemonService, private navbarService: NavbarService) {
|
||||
this.navbarLinks = [
|
||||
new NavbarLink('pills-relay-tx-tab', '#pills-relay-tx', 'pills-relay-tx', true, 'Relay Tx', true),
|
||||
|
@ -18,6 +20,8 @@ export class TransactionsComponent implements AfterViewInit {
|
|||
new NavbarLink('pills-flush-tx-pool-tab', '#pills-flush-tx-pool', 'pills-flush-tx-pool', false, 'Flush Tx Pool', true),
|
||||
new NavbarLink('pills-flush-cahe', '#pills-flush-cache', 'pills-flush-cache', false, 'Flush Cache', true)
|
||||
];
|
||||
|
||||
this.canRelay = false;
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
|
@ -27,4 +31,8 @@ export class TransactionsComponent implements AfterViewInit {
|
|||
private async load(): Promise<void> {
|
||||
|
||||
}
|
||||
|
||||
public async onRelay(): Promise<void> {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { CommonModule, NgClass, NgFor } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||
import { ChildActivationEnd, ChildActivationStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterEvent, RouterModule, RoutesRecognized } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
|
@ -7,22 +7,32 @@ import { ChildActivationEnd, ChildActivationStart, NavigationCancel, NavigationE
|
|||
templateUrl: './sidebar.component.html',
|
||||
styleUrl: './sidebar.component.scss'
|
||||
})
|
||||
export class SidebarComponent {
|
||||
public readonly navLinks: NavLink[];
|
||||
export class SidebarComponent implements OnChanges {
|
||||
@Input() public isDaemonRunning: boolean = false;
|
||||
|
||||
public navLinks: NavLink[];
|
||||
public isLoading: boolean;
|
||||
public errorMessage: string;
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.navLinks = [
|
||||
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
|
||||
new NavLink('Blockchain', '/blockchain', 'bi bi-bounding-box'),
|
||||
new NavLink('Transactions', '/transactions', 'bi bi-credit-card-2-front'),
|
||||
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('Bans', '/bans', 'bi bi-ban'),
|
||||
new NavLink('Settings', '/settings', 'bi bi-gear')
|
||||
];
|
||||
if (!this.isDaemonRunning) {
|
||||
this.navLinks = [
|
||||
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
|
||||
new NavLink('Settings', '/settings', 'bi bi-gear')
|
||||
];
|
||||
}
|
||||
else {
|
||||
this.navLinks = [
|
||||
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
|
||||
new NavLink('Blockchain', '/blockchain', 'bi bi-bounding-box'),
|
||||
new NavLink('Transactions', '/transactions', 'bi bi-credit-card-2-front'),
|
||||
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('Bans', '/bans', 'bi bi-ban'),
|
||||
new NavLink('Settings', '/settings', 'bi bi-gear')
|
||||
];
|
||||
}
|
||||
this.isLoading = false;
|
||||
this.errorMessage = '';
|
||||
}
|
||||
|
@ -31,6 +41,27 @@ export class SidebarComponent {
|
|||
return navLink.path == this.router.url;
|
||||
}
|
||||
|
||||
public ngOnChanges(changes: SimpleChanges): void {
|
||||
if (!this.isDaemonRunning) {
|
||||
this.navLinks = [
|
||||
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
|
||||
new NavLink('Settings', '/settings', 'bi bi-gear')
|
||||
];
|
||||
}
|
||||
else {
|
||||
this.navLinks = [
|
||||
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
|
||||
new NavLink('Blockchain', '/blockchain', 'bi bi-bounding-box'),
|
||||
new NavLink('Transactions', '/transactions', 'bi bi-credit-card-2-front'),
|
||||
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('Bans', '/bans', 'bi bi-ban'),
|
||||
new NavLink('Settings', '/settings', 'bi bi-gear')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class NavLink {
|
||||
|
|
31
src/common/AddedAuxPow.ts
Normal file
31
src/common/AddedAuxPow.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { AuxPoW } from "./AuxPoW";
|
||||
|
||||
export class AddedAuxPow {
|
||||
public readonly blockTemplateBlob: string;
|
||||
public readonly blockHashingBlob: string;
|
||||
public readonly merkleRoot: string;
|
||||
public readonly merkleTreeDepth: string;
|
||||
public auxPoW: AuxPoW[];
|
||||
|
||||
constructor(blockTemplateBlob: string, blockHashingBlob: string, merkleRoot: string, merkleTreeDepth: string, auxPoW: AuxPoW[]) {
|
||||
this.blockTemplateBlob = blockTemplateBlob;
|
||||
this.blockHashingBlob = blockHashingBlob;
|
||||
this.merkleRoot = merkleRoot;
|
||||
this.merkleTreeDepth = merkleTreeDepth;
|
||||
this.auxPoW = auxPoW;
|
||||
}
|
||||
|
||||
public static parse(addedAuxPow: any): AddedAuxPow {
|
||||
const blockTemplateBlob: string = addedAuxPow.blocktemplate_blob;
|
||||
const blockHashingBlob: string = addedAuxPow.blockhashing_blob;
|
||||
const merkleRoot: string = addedAuxPow.merkle_root;
|
||||
const merkleTreeDepth: string = addedAuxPow.merkle_tree_depth;
|
||||
const auxPoW: AuxPoW[] = [];
|
||||
|
||||
const _auxPoW: any[] | undefined = addedAuxPow.aux_pow;
|
||||
|
||||
if (_auxPoW) _auxPoW.forEach((_pow) => auxPoW.push(AuxPoW.parse(_pow)));
|
||||
|
||||
return new AddedAuxPow(blockTemplateBlob, blockHashingBlob, merkleRoot, merkleTreeDepth, auxPoW);
|
||||
}
|
||||
}
|
20
src/common/AuxPoW.ts
Normal file
20
src/common/AuxPoW.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
export class AuxPoW {
|
||||
public readonly id: string;
|
||||
public readonly hash: string;
|
||||
|
||||
constructor(id: string, hash: string) {
|
||||
this.id = id;
|
||||
this.hash = hash;
|
||||
}
|
||||
|
||||
public toDictionary(): { [key: string]: any } {
|
||||
return {
|
||||
'id': this.id,
|
||||
'hash': this.hash
|
||||
}
|
||||
}
|
||||
|
||||
public static parse(auxPoW: any): AuxPoW {
|
||||
return new AuxPoW(auxPoW.id, auxPoW.hash);
|
||||
}
|
||||
}
|
22
src/common/Block.ts
Normal file
22
src/common/Block.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { BlockDetails } from "./BlockDetails";
|
||||
import { BlockHeader } from "./BlockHeader";
|
||||
|
||||
export class Block {
|
||||
public readonly blob: string;
|
||||
public readonly blockHeader: BlockHeader;
|
||||
public readonly details: BlockDetails;
|
||||
|
||||
constructor(blob: string, blockHeader: BlockHeader, details: BlockDetails) {
|
||||
this.blob = blob;
|
||||
this.blockHeader = blockHeader;
|
||||
this.details = details;
|
||||
}
|
||||
|
||||
public static parse(block: any): Block {
|
||||
const blob = block.blob;
|
||||
const blockHeader = BlockHeader.parse(block.block_header);
|
||||
const details = BlockDetails.parse(block.json);
|
||||
|
||||
return new Block(blob, blockHeader, details);
|
||||
}
|
||||
}
|
34
src/common/BlockDetails.ts
Normal file
34
src/common/BlockDetails.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { MinerTx } from "./MinerTx";
|
||||
|
||||
export class BlockDetails {
|
||||
public majorVersion: number;
|
||||
public minorVersion: number;
|
||||
public timestamp: number;
|
||||
public prevId: string;
|
||||
public nonce: number;
|
||||
public minerTx: MinerTx;
|
||||
public txHashes: string[];
|
||||
|
||||
constructor(majorVersion: number, minorVersion: number, timestamp: number, prevId: string, nonce: number, minerTx: MinerTx, txHashes: string[]) {
|
||||
this.majorVersion = majorVersion;
|
||||
this.minorVersion = minorVersion;
|
||||
this.timestamp = timestamp;
|
||||
this.prevId = prevId;
|
||||
this.nonce = nonce;
|
||||
this.minerTx = minerTx;
|
||||
this.txHashes = txHashes;
|
||||
}
|
||||
|
||||
public static parse(details: string): BlockDetails {
|
||||
const blockDetails = JSON.parse(details);
|
||||
const majorVersion = blockDetails.major_version;
|
||||
const minorVersion = blockDetails.minor_version;
|
||||
const timestamp = blockDetails.timestamp;
|
||||
const prevId = blockDetails.prev_id;
|
||||
const nonce = blockDetails.nonce;
|
||||
const minerTx = MinerTx.parse(blockDetails.miner_tx);
|
||||
const txHashes = blockDetails.tx_hashes;
|
||||
|
||||
return new BlockDetails(majorVersion, minorVersion, timestamp, prevId, nonce, minerTx, txHashes);
|
||||
}
|
||||
}
|
31
src/common/CoinbaseTxSum.ts
Normal file
31
src/common/CoinbaseTxSum.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
export class CoinbaseTxSum {
|
||||
public readonly emissionAmount: number;
|
||||
public readonly emissionAmountTop64: number;
|
||||
public readonly feeAmount: number;
|
||||
public readonly feeAmountTop64: number;
|
||||
public readonly topHash: string;
|
||||
public readonly wideEmissionAmount: string;
|
||||
public readonly wideFeeAmount: string;
|
||||
|
||||
constructor(emissionAmount: number, emissionAmountTop64: number, feeAmount: number, feeAmountTop64: number, topHash: string, wideEmissionAmount: string, wideFeeAmount: string) {
|
||||
this.emissionAmount = emissionAmount;
|
||||
this.emissionAmountTop64 = emissionAmountTop64;
|
||||
this.feeAmount = feeAmount;
|
||||
this.feeAmountTop64 = feeAmountTop64;
|
||||
this.topHash = topHash;
|
||||
this.wideEmissionAmount = wideEmissionAmount;
|
||||
this.wideFeeAmount = wideFeeAmount;
|
||||
}
|
||||
|
||||
public static parse(coinbaseTxSum: any): CoinbaseTxSum {
|
||||
const emissionAmount = coinbaseTxSum.emission_amount;
|
||||
const emissionAmountTop64 = coinbaseTxSum.emission_amount_top64;
|
||||
const feeAmount = coinbaseTxSum.fee_amount;
|
||||
const feeAmountTop64 = coinbaseTxSum.fee_amount_top64;
|
||||
const topHash = coinbaseTxSum.top_hash;
|
||||
const wideEmissionAmount = coinbaseTxSum.wide_emission_amount;
|
||||
const wideFeeAmount = coinbaseTxSum.wide_fee_amount;
|
||||
|
||||
return new CoinbaseTxSum(emissionAmount, emissionAmountTop64, feeAmount, feeAmountTop64, topHash, wideEmissionAmount, wideFeeAmount);
|
||||
}
|
||||
}
|
|
@ -128,8 +128,121 @@ export class DaemonSettings {
|
|||
public rpcPaymentAllowFreeLoopback: boolean = false;
|
||||
public disableRpcBan: boolean = false;
|
||||
|
||||
public toCommandOptions(): string {
|
||||
let options: string = '';
|
||||
public equals(settings: DaemonSettings): boolean {
|
||||
return this.toCommandOptions().join('') == settings.toCommandOptions().join('');
|
||||
}
|
||||
|
||||
public clone(): DaemonSettings {
|
||||
return Object.assign(new DaemonSettings(), this);
|
||||
}
|
||||
|
||||
public static parse(data: any): DaemonSettings {
|
||||
const settings = new DaemonSettings();
|
||||
Object.assign(settings, data);
|
||||
return settings;
|
||||
}
|
||||
|
||||
public toCommandOptions(): string[] {
|
||||
const options: string[] = [];
|
||||
|
||||
if (this.mainnet) options.push(`--mainnet`);
|
||||
else if (this.testnet) options.push(`--testnet`);
|
||||
else if (this.stagenet) options.push(`--stagenet`);
|
||||
|
||||
if (this.logFile != '') options.push('--log-file', this.logFile);
|
||||
if (this.logLevel >= 0 && this.logLevel <= 4) options.push('--log-level', `${this.logLevel}`);
|
||||
if (this.maxLogFileSize) options.push(`--max-log-file-size=${this.maxLogFileSize}`);
|
||||
if (this.maxLogFiles) options.push(`--max-log-files=${this.maxLogFiles}`);
|
||||
if (this.maxConcurrency) options.push(`--max-concurrency=${this.maxConcurrency}`);
|
||||
if (this.proxy != '') options.push(`--proxy=${this.proxy}`);
|
||||
if (this.proxyAllowDnsLeaks) options.push(`--proxy-allow-dns-leaks`);
|
||||
if (this.publicNode) options.push(`--public-node`);
|
||||
if (this.noZmq) options.push(`--no-zmq`);
|
||||
if (!this.noZmq && this.zmqRpcBindIp != '') options.push(`--zmq-rpc-bind-ip`, this.zmqRpcBindIp);
|
||||
if (!this.noZmq && this.zmqRpcBindPort) options.push(`--zmq-rpc-bind-port`, `${this.zmqRpcBindPort}`);
|
||||
if (!this.noZmq && this.zmqPub != '') options.push(`--zmq-pub`, this.zmqPub);
|
||||
if (this.testDropDownload) options.push(`--test-drop-download`);
|
||||
if (this.testDropDownload && this.testDropDownloadHeight) options.push(`--test-drop-download-height`);
|
||||
if (this.testDbgLockSleep) options.push(`--tet-dbg-lock-sleep`, `${this.testDbgLockSleep}`);
|
||||
if (this.regtest) options.push(`--regtest`);
|
||||
if (this.keepFakeChain) options.push(`--keep-fakechain`);
|
||||
if (this.fixedDifficulty) options.push(`--fixed-difficulty`, `${this.fixedDifficulty}`);
|
||||
if (this.enforceDnsCheckpoint) options.push(`--enforce-dns-checkpoint`);
|
||||
if (this.prepBlocksThreads) options.push(`--prep-block-threads`, `${this.prepBlocksThreads}`);
|
||||
if (this.fastBlockSync) options.push(`--fast-block-sync`, `1`);
|
||||
if (this.showTimeStats) options.push(`--show-time-stats`);
|
||||
if (this.blockSyncSize) options.push(`--block-sync-size`, `${this.blockSyncSize}`);
|
||||
if (this.checkUpdates) options.push(`--check-updates`, this.checkUpdates);
|
||||
if (this.noFluffyBlocks) options.push(`--no-fluffy-blocks`);
|
||||
if (this.offline) options.push(`--offline`);
|
||||
if (this.disableDnsCheckpoints) options.push(`--disable-dns-checkpoints`);
|
||||
if (this.blockDownloadMaxSize) options.push(`--block-download-max-size`, `${this.blockDownloadMaxSize}`);
|
||||
if (this.syncPrunedBlocks) options.push(`--sync-pruned-blocks`);
|
||||
if (this.maxTxPoolWeight) options.push(`--max-txpool-weight`, `${this.maxTxPoolWeight}`);
|
||||
if (this.blockNotify != '') options.push(`--block-notify`, this.blockNotify);
|
||||
if (this.pruneBlockchain) options.push('--prune-blockchain');
|
||||
if (this.reorgNotify != '') options.push(`--reorg-notify`, this.reorgNotify);
|
||||
if (this.blockRateNotify != '') options.push(`--block-rate-notify`, this.blockRateNotify);
|
||||
if (this.keepAltBlocks) options.push(`--keep-alt-blocks`);
|
||||
if (this.extraMessagesFile != '') options.push(`--extra-messages-file`, this.extraMessagesFile);
|
||||
if (this.startMining != '') options.push(`--start-mining`, this.startMining);
|
||||
if (this.miningThreds) options.push(`--mining-threads`, `${this.miningThreds}`);
|
||||
if (this.bgMiningEnable) options.push(`--bg-mining-enable`);
|
||||
if (this.bgMiningIgnoreBattery) options.push(`--bg-mining-ignore-battery`);
|
||||
if (this.bgMiningIdleThreshold) options.push(`--bg-mining-idle-threshold`, `${this.bgMiningIdleThreshold}`);
|
||||
if (this.bgMiningMinIdleInterval) options.push(`--bg-mining-idle-interval`, `${this.bgMiningMinIdleInterval}`);
|
||||
if (this.bgMiningMinerTarget) options.push(`--bg-mining-miner-target`, `${this.bgMiningMinerTarget}`);
|
||||
if (this.dbSyncMode != '') options.push(`--db-sync-mode`, `${this.dbSyncMode}`);
|
||||
if (this.dbSalvage) options.push(`--db-salvage`);
|
||||
if (this.p2pBindIp != '') options.push(`--p2p-bind-ip`, this.p2pBindIp);
|
||||
if (this.p2pBindIpv6Address != '') options.push(`--p2p-bind-ipv6-address`, this.p2pBindIpv6Address);
|
||||
if (this.p2pBindPort) options.push(`--p2p-bind-port`, `${this.p2pBindPort}`);
|
||||
if (this.p2pBindPortIpv6) options.push(`--p2p-bind-port-ipv6`, `${this.p2pBindPortIpv6}`);
|
||||
if (this.p2pUseIpv6) options.push(`--p2p-use-ipv6`);
|
||||
if (this.p2pIgnoreIpv4) options.push(`--p2p-ignore-ipv4`);
|
||||
if (this.p2pExternalPort) options.push(`--p2p-external-port`, `${this.p2pExternalPort}`);
|
||||
if (this.allowLocalIp) options.push(`--allow-local-ip`);
|
||||
if (this.addPeer != '') options.push('--add-peer', this.addPeer);
|
||||
if (this.addPriorityNode != '') options.push(`--add-priority-node`, this.addPriorityNode);
|
||||
if (this.addExclusiveNode != '') options.push(`--add-exlcusive-node`, this.addExclusiveNode);
|
||||
if (this.seedNode != '') options.push(`--seed-node`, this.seedNode);
|
||||
if (this.txProxy != '') options.push(`--tx-proxy`, this.txProxy);
|
||||
if (this.anonymousInbound != '') options.push(`--anonymous-inbound`, this.anonymousInbound);
|
||||
if (this.banList != '') options.push(`--ban-list`, this.banList);
|
||||
if (this.hideMyPort) options.push(`--hide-my-port`);
|
||||
if (this.noSync) options.push(`--no-sync`);
|
||||
if (this.enableDnsBlocklist) options.push(`--enable-dns-block-list`);
|
||||
if (this.noIgd) options.push(`--no-igd`);
|
||||
if (this.outPeers >= 0) options.push(`--out-peers`, `${this.outPeers}`);
|
||||
if (this.inPeers >= 0) options.push(`--in-peers`, `${this.inPeers}`);
|
||||
if (this.tosFlag >= 0) options.push(`--tos-flag`, `${this.tosFlag}`);
|
||||
if (this.limitRate >= 0) options.push(`--limit-rate`, `${this.limitRate}`);
|
||||
if (this.limitRateUp >= 0) options.push(`--limit-rate-up`, `${this.limitRateUp}`);
|
||||
if (this.limitRateDown >= 0) options.push(`--limit-rate-down`, `${this.limitRateDown}`);
|
||||
if (this.padTransactions) options.push(`--pad-transactions`);
|
||||
if (this.maxConnectionsPerIp >= 0) options.push(`--max-connections-per-ip`, `${this.maxConnectionsPerIp}`);
|
||||
if (this.rpcBindIp != '') options.push(`--rpc-bind-ip`, `${this.rpcBindIp}`);
|
||||
if (this.rpcBindPort) options.push(`--rpc-bind-ip`, `${this.rpcBindIp}`);
|
||||
if (this.restrictedBindPort) options.push(`--restricted-bind-port`, `${this.restrictedBindPort}`);
|
||||
if (this.restrictedRpc) options.push(`--restricted-rpc`);
|
||||
if (this.bootstrapDaemonAddress != '') options.push(`--bootstrap-daemon-address`, this.bootstrapDaemonAddress);
|
||||
if (this.bootstrapDaemonLogin != '') options.push(`--bootstrap-daemon-login`, this.bootstrapDaemonLogin);
|
||||
if (this.bootstrapDaemonProxy != '') options.push(`--bootstrap-daemon-proxy`, this.bootstrapDaemonProxy);
|
||||
if (this.confirmExternalBind) options.push(`--confirm-external-bind`);
|
||||
if (this.rpcAccessControlOrigins != '') options.push(`--rpc-access-control-origins=${this.rpcAccessControlOrigins}`);
|
||||
if (this.rpcSsl) options.push(`--rpc-ssl`, this.rpcSsl);
|
||||
if (this.rpcSslPrivateKey) options.push(`--rpc-ssl-private-key`, this.rpcSslPrivateKey);
|
||||
if (this.rpcSslCertificate) options.push(`--rpc-ssl-certificate`, this.rpcSslCertificate);
|
||||
if (this.rpcSslCACertificates) options.push(`--rpc-ssl-ca-certificates`, this.rpcSslCACertificates);
|
||||
if (this.rpcAllowedFingerprints) options.push(`--rpc-allowed-fingerprints`, this.rpcAllowedFingerprints);
|
||||
if (this.rpcSslAllowChained) options.push(`--rpc-ssl-allow-chained`);
|
||||
if (this.rpcSslAllowAnyCert) options.push(`--rpc-ssl-allow-any-cert`);
|
||||
|
||||
if (this.rpcPaymentAddress != '') options.push(`--rpc-payment-address`, this.rpcPaymentAddress);
|
||||
if (this.rpcPaymentDifficuly) options.push(`--rpc-payment-difficulty`, `${this.rpcPaymentDifficuly}`);
|
||||
if (this.rpcPaymentCredits) options.push(`--rpc-payment-credits`, `${this.rpcPaymentCredits}`);
|
||||
if (this.rpcPaymentAllowFreeLoopback) options.push(`--rpc-payment-allow-free-loopback`);
|
||||
if (this.disableRpcBan) options.push(`--disable-rpc-ban`);
|
||||
|
||||
return options;
|
||||
}
|
||||
|
|
125
src/common/MinerTx.ts
Normal file
125
src/common/MinerTx.ts
Normal file
|
@ -0,0 +1,125 @@
|
|||
export class MinerTx {
|
||||
public readonly version: number;
|
||||
public readonly unlockTime: number;
|
||||
public readonly vin: TxInput[];
|
||||
public readonly vout: TxOutput[];
|
||||
public readonly extra: number[];
|
||||
public readonly rctSignatures: RctSignatures;
|
||||
|
||||
constructor(version: number, unlockTime: number, vin: TxInput[], vout: TxOutput[], extra: number[], rctSignatures: RctSignatures) {
|
||||
this.version = version;
|
||||
this.unlockTime = unlockTime;
|
||||
this.vin = vin;
|
||||
this.vout = vout;
|
||||
this.extra = extra;
|
||||
this.rctSignatures = rctSignatures;
|
||||
}
|
||||
|
||||
public static parse(minerTx: any): MinerTx {
|
||||
const version = minerTx.version;
|
||||
const unlockTime = minerTx.unlock_time;
|
||||
const _vin: any[] | undefined = minerTx.vin;
|
||||
const _vout: any[] | undefined = minerTx.vout;
|
||||
const extra = minerTx.extra;
|
||||
const rctSignatures = RctSignatures.parse(minerTx.rct_signatures);
|
||||
|
||||
const vin: TxInput[] = [];
|
||||
const vout: TxOutput[] = [];
|
||||
|
||||
if (_vin) _vin.forEach((v) => vin.push(TxInput.parse(v)));
|
||||
if (_vout) _vout.forEach((v) => vout.push(TxOutput.parse(v)));
|
||||
|
||||
return new MinerTx(version, unlockTime, vin, vout, extra, rctSignatures);
|
||||
}
|
||||
}
|
||||
|
||||
export class TxInput {
|
||||
public readonly gen: TxInputGen;
|
||||
|
||||
constructor(gen: TxInputGen) {
|
||||
this.gen = gen;
|
||||
}
|
||||
|
||||
public static parse(input: any): TxInput {
|
||||
const gen = TxInputGen.parse(input.gen);
|
||||
return new TxInput(gen);
|
||||
}
|
||||
}
|
||||
|
||||
export class TxInputGen {
|
||||
public readonly height: number;
|
||||
|
||||
constructor(height: number) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
public static parse(gen: any): TxInputGen {
|
||||
const height = gen.height;
|
||||
|
||||
return new TxInputGen(height);
|
||||
}
|
||||
}
|
||||
|
||||
export class TxOutput {
|
||||
public readonly amount: number;
|
||||
public readonly target: TxOutputTarget;
|
||||
|
||||
constructor(amount: number, target: TxOutputTarget) {
|
||||
this.amount = amount;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public static parse(out: any): TxOutput {
|
||||
const amount = out.amount;
|
||||
const target = TxOutputTarget.parse(out.target);
|
||||
|
||||
return new TxOutput(amount, target);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class RctSignatures {
|
||||
public readonly type: number;
|
||||
|
||||
constructor(type: number) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static parse(rctSignatures: any): RctSignatures {
|
||||
const type = rctSignatures.type;
|
||||
|
||||
return new RctSignatures(type);
|
||||
}
|
||||
}
|
||||
|
||||
export class TxOutputTarget {
|
||||
public readonly taggedKey: TaggedKey;
|
||||
|
||||
constructor(taggedKey: TaggedKey)
|
||||
{
|
||||
this.taggedKey = taggedKey;
|
||||
}
|
||||
|
||||
public static parse(target: any): TxOutputTarget {
|
||||
const taggedKey = TaggedKey.parse(target.tagged_key);
|
||||
|
||||
return new TxOutputTarget(taggedKey);
|
||||
}
|
||||
}
|
||||
|
||||
export class TaggedKey {
|
||||
public readonly key: string;
|
||||
public readonly viewTag: string;
|
||||
|
||||
constructor(key: string, viewTag: string) {
|
||||
this.key = key;
|
||||
this.viewTag = viewTag;
|
||||
}
|
||||
|
||||
public static parse(taggedKey: any): TaggedKey {
|
||||
const key = taggedKey.key;
|
||||
const viewTag = taggedKey.view_tag;
|
||||
|
||||
return new TaggedKey(key, viewTag);
|
||||
}
|
||||
}
|
91
src/common/MiningStatus.ts
Normal file
91
src/common/MiningStatus.ts
Normal file
|
@ -0,0 +1,91 @@
|
|||
export class MiningStatus {
|
||||
public readonly active: boolean;
|
||||
public readonly address: string;
|
||||
public readonly bgIdleThreshold: number;
|
||||
public readonly bgMinIdleSeconds: number;
|
||||
public readonly bgTarget: number;
|
||||
public readonly blockReward: number;
|
||||
public readonly blockTarget: number;
|
||||
public readonly difficulty: number;
|
||||
public readonly difficultyTop64: number;
|
||||
public readonly isBackgroundMiningEnabled: boolean;
|
||||
public readonly powAlgorithm: string;
|
||||
public readonly speed: number;
|
||||
public readonly threadsCount: number;
|
||||
public readonly wideDifficulty: string;
|
||||
|
||||
constructor(
|
||||
active: boolean,
|
||||
address: string,
|
||||
bgIdleThreshold: number,
|
||||
bgMinIdleSeconds: number,
|
||||
bgTarget: number,
|
||||
blockReward: number,
|
||||
blockTarget: number,
|
||||
difficulty: number,
|
||||
difficultyTop64: number,
|
||||
isBackgroundMiningEnabled: boolean,
|
||||
powAlgorithm: string,
|
||||
speed: number,
|
||||
threadsCount: number,
|
||||
wideDifficulty: string
|
||||
) {
|
||||
this.active = active;
|
||||
this.address = address;
|
||||
this.bgIdleThreshold = bgIdleThreshold;
|
||||
this.bgMinIdleSeconds = bgMinIdleSeconds;
|
||||
this.bgTarget = bgTarget;
|
||||
this.blockReward = blockReward;
|
||||
this.blockTarget = blockTarget;
|
||||
this.difficulty = difficulty;
|
||||
this.difficultyTop64 = difficultyTop64;
|
||||
this.isBackgroundMiningEnabled = isBackgroundMiningEnabled;
|
||||
this.powAlgorithm = powAlgorithm;
|
||||
this.speed = speed;
|
||||
this.threadsCount = threadsCount;
|
||||
this.wideDifficulty = wideDifficulty;
|
||||
}
|
||||
|
||||
public static parse(miningStatus: any): MiningStatus {
|
||||
return new MiningStatus(
|
||||
miningStatus.active,
|
||||
miningStatus.address,
|
||||
miningStatus.bg_idle_threshold,
|
||||
miningStatus.bg_min_idle_seconds,
|
||||
miningStatus.bg_target,
|
||||
miningStatus.block_reward,
|
||||
miningStatus.block_target,
|
||||
miningStatus.difficulty,
|
||||
miningStatus.difficulty_top64,
|
||||
miningStatus.is_background_mining_enabled,
|
||||
miningStatus.pow_algorithm,
|
||||
miningStatus.speed,
|
||||
miningStatus.threads_count,
|
||||
miningStatus.wide_difficulty
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {
|
||||
"active": true,
|
||||
"address": "47xu3gQpF569au9C2ajo5SSMrWji6xnoE5vhr94EzFRaKAGw6hEGFXYAwVADKuRpzsjiU1PtmaVgcjUJF89ghGPhUXkndHc",
|
||||
"bg_idle_threshold": 0,
|
||||
"bg_ignore_battery": false,
|
||||
"bg_min_idle_seconds": 0,
|
||||
"bg_target": 0,
|
||||
"block_reward": 1181637918707,
|
||||
"block_target": 120,
|
||||
"difficulty": 239928394679,
|
||||
"difficulty_top64": 0,
|
||||
"is_background_mining_enabled": false,
|
||||
"pow_algorithm": "RandomX",
|
||||
"speed": 23,
|
||||
"status": "OK",
|
||||
"threads_count": 1,
|
||||
"untrusted": false,
|
||||
"wide_difficulty": "0x37dcd8c3b7"
|
||||
}
|
||||
*/
|
||||
|
34
src/common/NetStats.ts
Normal file
34
src/common/NetStats.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
export class NetStats {
|
||||
public readonly startTime: number;
|
||||
public readonly totalPacketsIn: number;
|
||||
public readonly totalBytesIn: number;
|
||||
public readonly totalBytesOut: number;
|
||||
|
||||
constructor(startTime: number, totalPacketsIn: number, totalBytesIn: number, totalBytesOut: number) {
|
||||
this.startTime = startTime;
|
||||
this.totalPacketsIn = totalPacketsIn;
|
||||
this.totalBytesIn = totalBytesIn;
|
||||
this.totalBytesOut = totalBytesOut;
|
||||
}
|
||||
|
||||
public static parse(netStats: any): NetStats {
|
||||
const startTime = netStats.start_time;
|
||||
const totalPacketsIn = netStats.total_packets_in;
|
||||
const totalBytesIn = netStats.total_bytes_in;
|
||||
const totalBytesOut = netStats.total_bytes_out;
|
||||
|
||||
return new NetStats(startTime, totalPacketsIn, totalBytesIn, totalBytesOut);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* start_time - unsigned int; Unix start time.
|
||||
total_packets_in - unsigned int;
|
||||
total_bytes_in - unsigned int;
|
||||
total_packets_out - unsigned int;
|
||||
total_bytes_out - unsigned int;
|
||||
status - string; General RPC error code. "OK" means everything looks good.
|
||||
untrusted - boolean; States if the result is obtained using the bootstrap mode, and is therefore not trusted (true), or when the daemon is fully synced and thus handles the RPC locally (false).
|
||||
*/
|
35
src/common/OutKey.ts
Normal file
35
src/common/OutKey.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
|
||||
export class OutKey {
|
||||
public readonly height: number;
|
||||
public readonly key: string;
|
||||
public readonly mask: string;
|
||||
public readonly txId: string;
|
||||
public readonly unlocked: boolean;
|
||||
|
||||
constructor(height: number, key: string, mask: string, txId: string, unlocked: boolean) {
|
||||
this.height = height;
|
||||
this.key = key;
|
||||
this.mask = mask;
|
||||
this.txId = txId;
|
||||
this.unlocked = unlocked;
|
||||
}
|
||||
|
||||
public static parse(outkey: any): OutKey {
|
||||
const height = outkey.height;
|
||||
const key = outkey.key;
|
||||
const mask = outkey.mask;
|
||||
const txId = outkey.txid;
|
||||
const unlocked = outkey.unlocked;
|
||||
|
||||
return new OutKey(height, key, mask, txId, unlocked);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* height - unsigned int; block height of the output
|
||||
key - String; the public key of the output
|
||||
mask - String
|
||||
txid - String; transaction id
|
||||
unlocked - boolean; States if output is locked (false) or not (true)
|
||||
*/
|
16
src/common/Output.ts
Normal file
16
src/common/Output.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
export class Output {
|
||||
public readonly amount: number;
|
||||
public readonly index: number;
|
||||
|
||||
constructor(amount: number, index: number) {
|
||||
this.amount = amount;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public toDictionary(): { [key: string]: any } {
|
||||
return {
|
||||
'amount': this.amount,
|
||||
'index': this.index
|
||||
}
|
||||
}
|
||||
}
|
22
src/common/OutputDistribution.ts
Normal file
22
src/common/OutputDistribution.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
export class OutputDistribution {
|
||||
public readonly amount: number;
|
||||
public readonly base: number;
|
||||
public readonly distribution: number[];
|
||||
public readonly startHeight: number;
|
||||
|
||||
constructor(amount: number, base: number, distribution: number[], startHeight: number) {
|
||||
this.amount = amount;
|
||||
this.base = base;
|
||||
this.distribution = distribution;
|
||||
this.startHeight = startHeight;
|
||||
}
|
||||
|
||||
public static parse(outDistribution: any): OutputDistribution {
|
||||
const amount = outDistribution.amount;
|
||||
const base = outDistribution.base;
|
||||
const startHeight = outDistribution.start_height;
|
||||
const distribution = outDistribution.distribution;
|
||||
|
||||
return new OutputDistribution(amount, base, startHeight, distribution);
|
||||
}
|
||||
}
|
31
src/common/PublicNode.ts
Normal file
31
src/common/PublicNode.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
export class PublicNode {
|
||||
public readonly host: string;
|
||||
public readonly lastSeen: number;
|
||||
public readonly rpcCreditsPerHash: number;
|
||||
public readonly rpcPort: number;
|
||||
public readonly type: 'white' | 'gray';
|
||||
|
||||
constructor(type: 'white' | 'gray', host: string, lastSeen: number, rpcCreditsPerHash: number, rpcPort: number) {
|
||||
this.host = host;
|
||||
this.lastSeen = lastSeen;
|
||||
this.rpcCreditsPerHash = rpcCreditsPerHash;
|
||||
this.rpcPort = rpcPort;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static parse(publicNode: any, nodeType: 'white' | 'gray'): PublicNode {
|
||||
const host = publicNode.host;
|
||||
const lastSeen = publicNode.last_seen;
|
||||
const rpcCreditsPerHash = publicNode.rpc_credits_per_hash;
|
||||
const rpcPort = publicNode.rpc_port;
|
||||
|
||||
return new PublicNode(nodeType, host, lastSeen, rpcCreditsPerHash, rpcPort);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* host - string; The node's IP address. This includes IPv4, IPv6, Onion, and i2p addresses.
|
||||
last_seen - unsigned int; UNIX timestamp of the last time the node was seen.
|
||||
rpc_credits_per_hash - unsigned int; If payment for RPC is enabled, the number of credits the node is requesting per hash. Otherwise, 0.
|
||||
rpc_port - unsigned int; RPC port number of the node.
|
||||
*/
|
67
src/common/TxInfo.ts
Normal file
67
src/common/TxInfo.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
export class TxInfo {
|
||||
public readonly doubleSpend: boolean;
|
||||
public readonly feeTooLow: boolean;
|
||||
public readonly invalidInput: boolean;
|
||||
public readonly invalidOutput: boolean;
|
||||
public readonly lowMixin: boolean;
|
||||
public readonly notRct: boolean;
|
||||
public readonly notRelayed: boolean;
|
||||
public readonly overspend: boolean;
|
||||
public readonly reason: string;
|
||||
public readonly tooBig: boolean;
|
||||
|
||||
constructor(
|
||||
doubleSpend: boolean,
|
||||
feeTooLow: boolean,
|
||||
invalidInput: boolean,
|
||||
invalidOutput: boolean,
|
||||
lowMixin: boolean,
|
||||
notRct: boolean,
|
||||
notRelayed: boolean,
|
||||
overspend: boolean,
|
||||
reason: string,
|
||||
tooBig: boolean
|
||||
) {
|
||||
this.doubleSpend = doubleSpend;
|
||||
this.feeTooLow = feeTooLow;
|
||||
this.invalidInput = invalidInput;
|
||||
this.invalidOutput = invalidOutput;
|
||||
this.lowMixin = lowMixin;
|
||||
this.notRct = notRct;
|
||||
this.notRelayed = notRelayed;
|
||||
this.overspend = overspend;
|
||||
this.reason = reason;
|
||||
this.tooBig = tooBig;
|
||||
}
|
||||
|
||||
public static parse(txInfo: any): TxInfo {
|
||||
return new TxInfo(
|
||||
txInfo.double_spend,
|
||||
txInfo.fee_too_low,
|
||||
txInfo.invalid_input,
|
||||
txInfo.invalid_output,
|
||||
txInfo.low_mixin,
|
||||
txInfo.not_rct,
|
||||
txInfo.not_relayed,
|
||||
txInfo.overspend,
|
||||
txInfo.reason,
|
||||
txInfo.too_big
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* double_spend - boolean; Transaction is a double spend (true) or not (false).
|
||||
fee_too_low - boolean; Fee is too low (true) or OK (false).
|
||||
invalid_input - boolean; Input is invalid (true) or valid (false).
|
||||
invalid_output - boolean; Output is invalid (true) or valid (false).
|
||||
low_mixin - boolean; Mixin count is too low (true) or OK (false).
|
||||
not_rct - boolean; Transaction is a standard ring transaction (true) or a ring confidential transaction (false).
|
||||
not_relayed - boolean; Transaction was not relayed (true) or relayed (false).
|
||||
overspend - boolean; Transaction uses more money than available (true) or not (false).
|
||||
reason - string; Additional information. Currently empty or "Not relayed" if transaction was accepted but not relayed.
|
||||
status - string; General RPC error code. "OK" means everything looks good. Any other value means that something went wrong.
|
||||
too_big - boolean; Transaction size is too big (true) or OK (false).
|
||||
untrusted - boolean; States if the result is obtained using the bootstrap mode, and is therefore not trusted (true), or when the daemon is fully synced and thus handles the RPC locally (false)
|
||||
*/
|
28
src/common/UpdateInfo.ts
Normal file
28
src/common/UpdateInfo.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
export class UpdateInfo {
|
||||
public readonly autoUri: string;
|
||||
public readonly hash: string;
|
||||
public readonly path: string;
|
||||
public readonly update: boolean;
|
||||
public readonly userUri: string;
|
||||
public readonly version: string;
|
||||
|
||||
constructor(autoUri: string, hash: string, path: string, update: boolean, userUri: string, version: string) {
|
||||
this.autoUri = autoUri;
|
||||
this.hash = hash;
|
||||
this.path = path;
|
||||
this.update = update;
|
||||
this.userUri = userUri;
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public static parse(info: any): UpdateInfo {
|
||||
const autoUri = info.auto_uri;
|
||||
const hash = info.hash;
|
||||
const path = info.path;
|
||||
const update = info.update;
|
||||
const userUri = info.user_uri;
|
||||
const version = info.version;
|
||||
|
||||
return new UpdateInfo(autoUri, hash, path, update, userUri, version);
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
export { RpcError } from "./error/RpcError";
|
||||
export { CoreIsBusyError } from "./error/CoreIsBusyError";
|
||||
export { MethodNotFoundError } from "./error/MethodNotFoundError";
|
||||
|
|
8
src/common/error/MethodNotFoundError.ts
Normal file
8
src/common/error/MethodNotFoundError.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { RpcError } from "./RpcError";
|
||||
|
||||
export class MethodNotFoundError extends RpcError {
|
||||
|
||||
constructor() {
|
||||
super(-32601, 'Method not found');
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
export { RPCRequest } from "./request/RPCRequest";
|
||||
export { JsonRPCRequest } from "./request/JsonRPCRequest";
|
||||
export { GetBlockRequest } from "./request/GetBlockRequest";
|
||||
export { GetBlockCountRequest } from "./request/GetBlockCountRequest";
|
||||
export { GetBlockHashRequest } from "./request/GetBlockHashRequest";
|
||||
export { GetBlockTemplateRequest } from "./request/GetBlockTemplateRequest";
|
||||
|
@ -16,7 +17,9 @@ export { SetBansRequest } from "./request/SetBansRequest";
|
|||
export { GetBansRequest } from "./request/GetBansRequest";
|
||||
export { BannedRequest } from "./request/BannedRequest";
|
||||
export { FlushTxPoolRequest } from "./request/FlushTxPoolRequest";
|
||||
export { GetOutsRequest } from "./request/GetOutsRequest";
|
||||
export { GetOutputHistogramRequest } from "./request/GetOutputHistogramRequest";
|
||||
export { GetOutputDistributionRequest } from "./request/GetOutputDistributionRequest";
|
||||
export { SyncInfoRequest } from "./request/SyncInfoRequest";
|
||||
export { GetVersionRequest } from "./request/GetVersionRequest";
|
||||
export { GetFeeEstimateRequest } from "./request/GetFeeEstimateRequest";
|
||||
|
@ -28,7 +31,29 @@ export { CalculatePoWHashRequest } from "./request/CalculatePoWHashRequest";
|
|||
export { FlushCacheRequest } from "./request/FlushCacheRequest";
|
||||
export { GetMinerDataRequest } from "./request/GetMinerDataRequest";
|
||||
export { GetCoinbaseTxSumRequest } from "./request/GetCoinbaseTxSumRequest";
|
||||
export { AddAuxPoWRequest } from "./request/AddAuxPoWRequest";
|
||||
export { EmptyRpcRequest } from "./request/EmptyRpcRequest";
|
||||
export { UpdateRequest } from "./request/UpdateRequest";
|
||||
export { CheckUpdateRequest } from "./request/CheckUpdateRequest";
|
||||
export { DownloadUpdateRequest } from "./request/DownloadUpdateRequest";
|
||||
|
||||
export { PopBlocksRequest } from "./request/PopBlocksRequest";
|
||||
export { GetTransactionPoolHashesRequest } from "./request/GetTransactionPoolHashesRequest";
|
||||
export { GetTransactionPoolHashesBinaryRequest } from "./request/GetTransactionPoolHashesBinaryRequest";
|
||||
export { GetPublicNodesRequest } from "./request/GetPublicNodesRequest";
|
||||
export { GetNetStatsRequest } from "./request/GetNetStatsRequest";
|
||||
export { InPeersRequest } from "./request/InPeersRequest";
|
||||
export { OutPeersRequest } from "./request/OutPeersRequest";
|
||||
export { SetLimitRequest } from "./request/SetLimitRequest";
|
||||
export { StopDaemonRequest } from "./request/StopDaemonRequest";
|
||||
export { MiningStatusRequest } from "./request/MiningStatusRequest";
|
||||
export { StartMiningRequest } from "./request/StartMiningRequest";
|
||||
export { StopMiningRequest } from "./request/StopMiningRequest";
|
||||
export { SendRawTransactionRequest } from "./request/SendRawTransactionRequest";
|
||||
export { IsKeyImageSpentRequest } from "./request/IsKeyImageSpentRequest";
|
||||
export { GetAltBlockHashesRequest } from "./request/GetAltBlockHashesRequest";
|
||||
export { SaveBcRequest } from "./request/SaveBcRequest";
|
||||
export { SetBootstrapDaemonRequest } from "./request/SetBootstrapDaemonRequest";
|
||||
|
||||
/**
|
||||
* Restricted requests
|
||||
|
|
30
src/common/request/AddAuxPoWRequest.ts
Normal file
30
src/common/request/AddAuxPoWRequest.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { AuxPoW } from "../AuxPoW";
|
||||
import { JsonRPCRequest } from "./JsonRPCRequest";
|
||||
|
||||
export class AddAuxPoWRequest extends JsonRPCRequest {
|
||||
public override readonly method: string = 'add_aux_pow';
|
||||
public override readonly restricted: boolean = true;
|
||||
|
||||
public readonly blockTemplateBlob: string;
|
||||
public readonly auxPoW: AuxPoW[];
|
||||
|
||||
constructor(blockTemplateBlob: string, auxPoW: AuxPoW[]) {
|
||||
super();
|
||||
this.blockTemplateBlob = blockTemplateBlob;
|
||||
this.auxPoW = auxPoW;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
const dict = super.toDictionary();
|
||||
const auxPow: { [key: string]: any }[] = [];
|
||||
|
||||
this.auxPoW.forEach((pow) => auxPow.push(pow.toDictionary()));
|
||||
|
||||
dict['params'] = {
|
||||
'blocktemplate_blob': this.blockTemplateBlob,
|
||||
'aux_pow': auxPow
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
9
src/common/request/CheckUpdateRequest.ts
Normal file
9
src/common/request/CheckUpdateRequest.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { UpdateRequest } from "./UpdateRequest";
|
||||
|
||||
export class CheckUpdateRequest extends UpdateRequest {
|
||||
public override readonly command: "check" = "check";
|
||||
|
||||
constructor() {
|
||||
super("check", '');
|
||||
}
|
||||
}
|
10
src/common/request/DownloadUpdateRequest.ts
Normal file
10
src/common/request/DownloadUpdateRequest.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { UpdateRequest } from "./UpdateRequest";
|
||||
|
||||
export class DownloadUpdateRequest extends UpdateRequest {
|
||||
|
||||
public override readonly command: "download" = "download";
|
||||
|
||||
constructor(path: string = '') {
|
||||
super("download", path);
|
||||
}
|
||||
}
|
14
src/common/request/GetAltBlockHashesRequest.ts
Normal file
14
src/common/request/GetAltBlockHashesRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class GetAltBlockHashesRequest extends RPCRequest {
|
||||
public override readonly method: 'get_alt_block_hashes' = 'get_alt_block_hashes';
|
||||
public override readonly restricted: false = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {};
|
||||
}
|
||||
}
|
54
src/common/request/GetBlockRequest.ts
Normal file
54
src/common/request/GetBlockRequest.ts
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { JsonRPCRequest } from "./JsonRPCRequest";
|
||||
|
||||
export class GetBlockRequest extends JsonRPCRequest {
|
||||
public override readonly method: string = 'get_block';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
public readonly height: number;
|
||||
public readonly hash: string;
|
||||
public readonly fillPoWHash: boolean;
|
||||
|
||||
constructor(heightOrHash: number | string, fillPoWHash: boolean = false) {
|
||||
super();
|
||||
|
||||
if (typeof heightOrHash == 'number') {
|
||||
this.height = heightOrHash;
|
||||
this.hash = '';
|
||||
}
|
||||
else if (typeof heightOrHash == 'string') {
|
||||
this.hash = heightOrHash;
|
||||
this.height = -1;
|
||||
}
|
||||
else {
|
||||
throw new Error('Invalid paramater heightOrHash, must be a number or a string');
|
||||
}
|
||||
|
||||
this.fillPoWHash = fillPoWHash;
|
||||
}
|
||||
|
||||
public get byHash(): boolean {
|
||||
return this.hash != '' && this.height < 0;
|
||||
}
|
||||
|
||||
public get byHeight(): boolean {
|
||||
return this.hash == '' && this.height >= 0;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
const dict = super.toDictionary();
|
||||
|
||||
let params: { [key: string]: any } = { 'fill_pow_hash': this.fillPoWHash };
|
||||
|
||||
if (this.byHeight) {
|
||||
params['height'] = this.height;
|
||||
}
|
||||
else if (this.byHash) {
|
||||
params['hash'] = this.hash;
|
||||
}
|
||||
|
||||
dict['params'] = params;
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
}
|
14
src/common/request/GetNetStatsRequest.ts
Normal file
14
src/common/request/GetNetStatsRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class GetNetStatsRequest extends RPCRequest {
|
||||
public override readonly method: 'get_net_stats' = 'get_net_stats';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return { };
|
||||
}
|
||||
}
|
33
src/common/request/GetOutputDistributionRequest.ts
Normal file
33
src/common/request/GetOutputDistributionRequest.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { JsonRPCRequest } from "./JsonRPCRequest";
|
||||
|
||||
export class GetOutputDistributionRequest extends JsonRPCRequest {
|
||||
public override readonly method: string = 'get_output_distribution';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
public readonly amounts: number[];
|
||||
public readonly cumulative: boolean;
|
||||
public readonly fromHeight: number;
|
||||
public readonly toHeight: number;
|
||||
|
||||
constructor(amounts: number[], cumulative: boolean, fromHeight: number, toHeight: number) {
|
||||
super();
|
||||
|
||||
this.amounts = amounts;
|
||||
this.cumulative = cumulative;
|
||||
this.fromHeight = fromHeight;
|
||||
this.toHeight = toHeight;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
const dict = super.toDictionary();
|
||||
|
||||
dict['params'] = {
|
||||
'amounts': this.amounts,
|
||||
'cumulative': this.cumulative,
|
||||
'from_height': this.fromHeight,
|
||||
'to_height': this.toHeight
|
||||
};
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
26
src/common/request/GetOutsRequest.ts
Normal file
26
src/common/request/GetOutsRequest.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { Output } from "../Output";
|
||||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class GetOutsRequest extends RPCRequest {
|
||||
public override readonly method: string = 'get_outs';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
public readonly outputs: Output[];
|
||||
public readonly getTxId: boolean;
|
||||
|
||||
constructor(outputs: Output[], getTxId: boolean) {
|
||||
super();
|
||||
this.outputs = outputs;
|
||||
this.getTxId = getTxId;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
const outputs: { [key: string]: any }[] = [];
|
||||
this.outputs.forEach((output) => outputs.push(output.toDictionary()))
|
||||
|
||||
return {
|
||||
'outputs': outputs,
|
||||
'get_txid': this.getTxId
|
||||
}
|
||||
}
|
||||
}
|
26
src/common/request/GetPublicNodesRequest.ts
Normal file
26
src/common/request/GetPublicNodesRequest.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class GetPublicNodesRequest extends RPCRequest {
|
||||
public override readonly method: 'get_public_nodes' = 'get_public_nodes';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
public readonly whites: boolean;
|
||||
public readonly grays: boolean;
|
||||
public readonly includeBlocked: boolean;
|
||||
|
||||
constructor(whites: boolean = true, grays: boolean = false, includeBlocked: boolean = false) {
|
||||
super();
|
||||
|
||||
this.whites = whites;
|
||||
this.grays = grays;
|
||||
this.includeBlocked = includeBlocked;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
'whites': this.whites,
|
||||
'grays': this.grays,
|
||||
'include_blocked': this.includeBlocked
|
||||
};
|
||||
}
|
||||
}
|
14
src/common/request/GetTransactionPoolHashesBinaryRequest.ts
Normal file
14
src/common/request/GetTransactionPoolHashesBinaryRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCBinaryRequest } from "./RPCBinaryRequest";
|
||||
|
||||
export class GetTransactionPoolHashesBinaryRequest extends RPCBinaryRequest {
|
||||
public override readonly method: 'get_transaction_pool_hashes.bin' = 'get_transaction_pool_hashes.bin';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return { };
|
||||
}
|
||||
}
|
14
src/common/request/GetTransactionPoolHashesRequest.ts
Normal file
14
src/common/request/GetTransactionPoolHashesRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class GetTransactionPoolHashesRequest extends RPCRequest {
|
||||
public override readonly method: 'get_transaction_pool_hashes' = 'get_transaction_pool_hashes';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return { };
|
||||
}
|
||||
}
|
20
src/common/request/InPeersRequest.ts
Normal file
20
src/common/request/InPeersRequest.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class InPeersRequest extends RPCRequest {
|
||||
public override readonly method: 'in_peers' = 'in_peers';
|
||||
public override readonly restricted: false = false;
|
||||
|
||||
public readonly inPeers: number;
|
||||
|
||||
constructor(inPeers: number) {
|
||||
super();
|
||||
|
||||
this.inPeers = inPeers;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
"in_peers": this.inPeers
|
||||
};
|
||||
}
|
||||
}
|
19
src/common/request/IsKeyImageSpentRequest.ts
Normal file
19
src/common/request/IsKeyImageSpentRequest.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class IsKeyImageSpentRequest extends RPCRequest {
|
||||
public override readonly method: 'is_key_image_spent' = 'is_key_image_spent';
|
||||
public override readonly restricted: false = false;
|
||||
|
||||
public readonly keyImages: string[];
|
||||
|
||||
constructor(keyImages: string[]) {
|
||||
super();
|
||||
this.keyImages = keyImages;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
'key_images': this.keyImages
|
||||
}
|
||||
}
|
||||
}
|
14
src/common/request/MiningStatusRequest.ts
Normal file
14
src/common/request/MiningStatusRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class MiningStatusRequest extends RPCRequest {
|
||||
public override readonly method: 'mining_status' = 'mining_status';
|
||||
public override readonly restricted: true = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {};
|
||||
}
|
||||
}
|
20
src/common/request/OutPeersRequest.ts
Normal file
20
src/common/request/OutPeersRequest.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class OutPeersRequest extends RPCRequest {
|
||||
public override readonly method: 'out_peers' = 'out_peers';
|
||||
public override readonly restricted: false = false;
|
||||
|
||||
public readonly outPeers: number;
|
||||
|
||||
constructor(outPeers: number) {
|
||||
super();
|
||||
|
||||
this.outPeers = outPeers;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
"out_peers": this.outPeers
|
||||
};
|
||||
}
|
||||
}
|
20
src/common/request/PopBlocksRequest.ts
Normal file
20
src/common/request/PopBlocksRequest.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class PopBlocksRequest extends RPCRequest {
|
||||
public override readonly method: string = 'pop_blocks';
|
||||
public override readonly restricted: boolean = false;
|
||||
|
||||
public readonly nBlocks: number;
|
||||
|
||||
constructor(nBlocks: number) {
|
||||
super();
|
||||
|
||||
this.nBlocks = nBlocks;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
'nblocks': this.nBlocks
|
||||
}
|
||||
}
|
||||
}
|
5
src/common/request/RPCBinaryRequest.ts
Normal file
5
src/common/request/RPCBinaryRequest.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export abstract class RPCBinaryRequest extends RPCRequest {
|
||||
|
||||
}
|
14
src/common/request/SaveBcRequest.ts
Normal file
14
src/common/request/SaveBcRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class SaveBcRequest extends RPCRequest {
|
||||
public override readonly method: 'save_bc' = 'save_bc';
|
||||
public override readonly restricted: true = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public toDictionary(): { [key: string]: any; } {
|
||||
return {};
|
||||
}
|
||||
}
|
22
src/common/request/SendRawTransactionRequest.ts
Normal file
22
src/common/request/SendRawTransactionRequest.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class SendRawTransactionRequest extends RPCRequest {
|
||||
public override readonly method: 'send_raw_transaction' = 'send_raw_transaction';
|
||||
public override readonly restricted: false = false;
|
||||
|
||||
public readonly txAsHex: string;
|
||||
public readonly doNotRelay: boolean;
|
||||
|
||||
constructor(txAsHex: string, doNotRelay: boolean = false) {
|
||||
super();
|
||||
this.txAsHex = txAsHex;
|
||||
this.doNotRelay = doNotRelay;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
"tx_as_hex": this.txAsHex,
|
||||
"do_not_relay": this.doNotRelay
|
||||
}
|
||||
}
|
||||
}
|
28
src/common/request/SetBootstrapDaemonRequest.ts
Normal file
28
src/common/request/SetBootstrapDaemonRequest.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class SetBootstrapDaemonRequest extends RPCRequest {
|
||||
public override readonly method: 'set_bootstrap_daemon' = 'set_bootstrap_daemon';
|
||||
public override readonly restricted: true = true;
|
||||
|
||||
public readonly address: string;
|
||||
public readonly username: string;
|
||||
public readonly password: string;
|
||||
public readonly proxy: string;
|
||||
|
||||
constructor(address: string, username: string, password: string, proxy: string) {
|
||||
super();
|
||||
this.address = address;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
'address': this.address,
|
||||
'username': this.username,
|
||||
'password': this.password,
|
||||
'proxy': this.proxy
|
||||
}
|
||||
}
|
||||
}
|
22
src/common/request/SetLimitRequest.ts
Normal file
22
src/common/request/SetLimitRequest.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { RPCRequest } from "./RPCRequest"
|
||||
|
||||
export class SetLimitRequest extends RPCRequest {
|
||||
public override readonly method: 'set_limit' = 'set_limit';
|
||||
public override readonly restricted: true = true;
|
||||
|
||||
public readonly limitDown: number;
|
||||
public readonly limitUp: number;
|
||||
|
||||
constructor(limitDown: number, limitUp: number) {
|
||||
super();
|
||||
this.limitDown = limitDown;
|
||||
this.limitUp = limitUp;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
'limit_down': this.limitDown,
|
||||
'limit_up': this.limitUp
|
||||
}
|
||||
}
|
||||
}
|
27
src/common/request/StartMiningRequest.ts
Normal file
27
src/common/request/StartMiningRequest.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class StartMiningRequest extends RPCRequest {
|
||||
public override readonly method: 'start_mining' = 'start_mining';
|
||||
public override readonly restricted: true = true;
|
||||
public readonly doBackgroundMining: boolean;
|
||||
public readonly ignoreBattery: boolean;
|
||||
public readonly minerAddress: string;
|
||||
public readonly threadsCount: number;
|
||||
|
||||
constructor(doBackgroundMining: boolean, ignoreBattery: boolean, minerAddress: string, threadsCount: number) {
|
||||
super();
|
||||
this.doBackgroundMining = doBackgroundMining;
|
||||
this.ignoreBattery = ignoreBattery;
|
||||
this.minerAddress = minerAddress;
|
||||
this.threadsCount = threadsCount;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {
|
||||
"do_background_mining": this.doBackgroundMining,
|
||||
"ignore_battery": this.ignoreBattery,
|
||||
"miner_address": this.minerAddress,
|
||||
"threads_count": this.threadsCount
|
||||
}
|
||||
}
|
||||
}
|
14
src/common/request/StopDaemonRequest.ts
Normal file
14
src/common/request/StopDaemonRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class StopDaemonRequest extends RPCRequest {
|
||||
public override readonly method: 'stop_daemon' = 'stop_daemon';
|
||||
public override readonly restricted: true = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
return {};
|
||||
}
|
||||
}
|
14
src/common/request/StopMiningRequest.ts
Normal file
14
src/common/request/StopMiningRequest.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class StopMiningRequest extends RPCRequest {
|
||||
public override readonly method: 'stop_mining' = 'stop_mining';
|
||||
public override readonly restricted: true = true;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public toDictionary(): { [key: string]: any; } {
|
||||
return {};
|
||||
}
|
||||
}
|
27
src/common/request/UpdateRequest.ts
Normal file
27
src/common/request/UpdateRequest.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { RPCRequest } from "./RPCRequest";
|
||||
|
||||
export class UpdateRequest extends RPCRequest {
|
||||
public override readonly method: string = 'update';
|
||||
public override readonly restricted: boolean = true;
|
||||
|
||||
public readonly command: 'check' | 'download';
|
||||
public readonly path: string;
|
||||
|
||||
constructor(command: 'check' | 'download', path: string = '') {
|
||||
super();
|
||||
this.command = command;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public override toDictionary(): { [key: string]: any; } {
|
||||
const dict: { [key: string]: any } = {
|
||||
'command': this.command
|
||||
};
|
||||
|
||||
if (this.path != '') {
|
||||
dict['path'] = this.path;
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,6 @@
|
|||
<link rel="icon" type="image/x-icon" href="assets/icons/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root>Loading...</app-root>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue