Wide implementation

This commit is contained in:
everoddandeven 2024-09-21 13:37:49 +02:00
parent f8230e9cc5
commit 334a124a4a
92 changed files with 2757 additions and 66 deletions

BIN
.nx/cache/18.3.4-nx.linux-x64-gnu.node vendored Normal file

Binary file not shown.

View file

@ -3,7 +3,8 @@
"cli": {
"schematicCollections": [
"@angular-eslint/schematics"
]
],
"analytics": false
},
"version": 1,
"newProjectRoot": "projects",
@ -36,9 +37,15 @@
"src/assets"
],
"styles": [
"src/styles.scss"
"src/styles.scss",
"node_modules/bootstrap-icons/font/bootstrap-icons.css",
"node_modules/bootstrap-table/dist/bootstrap-table.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
"node_modules/bootstrap-table/dist/bootstrap-table.js"
],
"scripts": [],
"customWebpackConfig": {
"path": "./angular.webpack.js",
"replaceDuplicatePlugins": true
@ -142,7 +149,7 @@
"options": {
"polyfills": ["src/polyfills-test.ts"],
"tsConfig": "src/tsconfig.spec.json",
"globalMocks": ["styleTransform", "matchMedia", "getComputedStyle"],
"globalMocks": ["styleTransform", "matchMedia", "getComputedStyle"]
}
},
"lint": {

85
package-lock.json generated
View file

@ -17,6 +17,10 @@
"@angular/platform-browser": "17.3.6",
"@angular/platform-browser-dynamic": "17.3.6",
"@angular/router": "17.3.6",
"bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3",
"bootstrap-table": "1.23.2",
"jquery": "3.7.1",
"rxjs": "7.8.1",
"tslib": "2.6.2",
"zone.js": "0.14.4"
@ -35,7 +39,9 @@
"@ngx-translate/core": "15.0.0",
"@ngx-translate/http-loader": "8.0.0",
"@playwright/test": "1.43.1",
"@types/bootstrap": "5.2.10",
"@types/jest": "29.5.12",
"@types/jquery": "3.5.30",
"@types/node": "20.12.7",
"@typescript-eslint/eslint-plugin": "7.7.1",
"@typescript-eslint/parser": "7.7.1",
@ -5050,6 +5056,15 @@
"node": ">=16"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.0.tgz",
@ -5530,6 +5545,15 @@
"@types/node": "*"
}
},
"node_modules/@types/bootstrap": {
"version": "5.2.10",
"resolved": "https://registry.npmjs.org/@types/bootstrap/-/bootstrap-5.2.10.tgz",
"integrity": "sha512-F2X+cd6551tep0MvVZ6nM8v7XgGN/twpdNDjqS1TUM7YFNEtQYWk+dKAnH+T1gr6QgCoGMPl487xw/9hXooa2g==",
"dev": true,
"dependencies": {
"@popperjs/core": "^2.9.2"
}
},
"node_modules/@types/cacheable-request": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
@ -5693,6 +5717,15 @@
"pretty-format": "^29.0.0"
}
},
"node_modules/@types/jquery": {
"version": "3.5.30",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.30.tgz",
"integrity": "sha512-nbWKkkyb919DOUxjmRVk8vwtDb0/k8FKncmUKFi+NY+QXqWltooxTrswvz4LspQwxvLdvzBN1TImr6cw3aQx2A==",
"dev": true,
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/jsdom": {
"version": "20.0.1",
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
@ -5835,6 +5868,12 @@
"@types/send": "*"
}
},
"node_modules/@types/sizzle": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz",
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
"dev": true
},
"node_modules/@types/sockjs": {
"version": "0.3.36",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz",
@ -7849,6 +7888,47 @@
"dev": true,
"optional": true
},
"node_modules/bootstrap": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
"integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/bootstrap-table": {
"version": "1.23.2",
"resolved": "https://registry.npmjs.org/bootstrap-table/-/bootstrap-table-1.23.2.tgz",
"integrity": "sha512-1IFiWFZzbKlleXgYEHdwHkX6rxlQMEx2N1tA8rJK/j08pI+NjIGnxFeXUL26yQLQ0U135eis/BX3OV1+anY25g==",
"peerDependencies": {
"jquery": "3"
}
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@ -16200,6 +16280,11 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",

View file

@ -54,6 +54,10 @@
"@angular/platform-browser": "17.3.6",
"@angular/platform-browser-dynamic": "17.3.6",
"@angular/router": "17.3.6",
"bootstrap": "5.3.3",
"bootstrap-icons": "1.11.3",
"bootstrap-table": "1.23.2",
"jquery": "3.7.1",
"rxjs": "7.8.1",
"tslib": "2.6.2",
"zone.js": "0.14.4"
@ -72,7 +76,9 @@
"@ngx-translate/core": "15.0.0",
"@ngx-translate/http-loader": "8.0.0",
"@playwright/test": "1.43.1",
"@types/bootstrap": "5.2.10",
"@types/jest": "29.5.12",
"@types/jquery": "3.5.30",
"@types/node": "20.12.7",
"@typescript-eslint/eslint-plugin": "7.7.1",
"@typescript-eslint/parser": "7.7.1",

View file

@ -4,11 +4,12 @@ import { PageNotFoundComponent } from './shared/components';
import { HomeRoutingModule } from './home/home-routing.module';
import { DetailRoutingModule } from './detail/detail-routing.module';
import { HardForkInfoRoutingModule } from './hard-fork-info/hard-fork-info-routing.module';
const routes: Routes = [
{
path: '',
redirectTo: 'home',
redirectTo: 'detail',
pathMatch: 'full'
},
{
@ -21,7 +22,8 @@ const routes: Routes = [
imports: [
RouterModule.forRoot(routes, {}),
HomeRoutingModule,
DetailRoutingModule
DetailRoutingModule,
HardForkInfoRoutingModule
],
exports: [RouterModule]
})

View file

@ -1 +1,7 @@
<router-outlet></router-outlet>
<app-navbar></app-navbar>
<div class="d-flex">
<app-sidebar></app-sidebar>
<div class="d-flex">
<router-outlet></router-outlet>
</div>
</div>

View file

@ -1,3 +1,7 @@
:host {
}
body {
background-color: #373636;
}

View file

@ -15,6 +15,10 @@ import { HomeModule } from './home/home.module';
import { DetailModule } from './detail/detail.module';
import { AppComponent } from './app.component';
import { SidebarComponent } from "./sidebar/sidebar.component";
import { BansModule } from './bans/bans.module';
import { NavbarComponent } from "./navbar/navbar.component";
import { MiningModule } from './mining/mining.module';
// AoT requires an exported function for factories
const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new TranslateHttpLoader(http, './assets/i18n/', '.json');
@ -29,15 +33,19 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
SharedModule,
HomeModule,
DetailModule,
BansModule,
MiningModule,
AppRoutingModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: httpLoaderFactory,
deps: [HttpClient]
}
})
],
loader: {
provide: TranslateLoader,
useFactory: httpLoaderFactory,
deps: [HttpClient]
}
}),
SidebarComponent,
NavbarComponent
],
providers: [],
bootstrap: [AppComponent]
})

View file

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { BansComponent } from './bans.component';
const routes: Routes = [{
path: 'bans',
component: BansComponent
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class BansRoutingModule { }

View file

@ -0,0 +1,14 @@
<table
id="bansTable"
data-toggle="bansTable"
data-toolbar="#toolbar"
data-height="460"
>
<thead>
<tr>
<th data-field="host">Host</th>
<th data-field="ip">Ip</th>
<th data-field="seconds">Seconds</th>
</tr>
</thead>
</table>

View file

View file

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

View file

@ -0,0 +1,56 @@
import { AfterViewInit, Component } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { NavbarService } from '../navbar/navbar.service';
import { DaemonService } from '../core/services/daemon/daemon.service';
@Component({
selector: 'app-bans',
templateUrl: './bans.component.html',
styleUrl: './bans.component.scss'
})
export class BansComponent implements AfterViewInit {
constructor(private router: Router, private daemonService: DaemonService, private navbarService: NavbarService) {
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
if (event.url != '/bans') return;
this.onNavigationEnd();
}
})
}
ngAfterViewInit(): void {
console.log('BansComponent AFTER VIEW INIT');
setTimeout(() => {
const $table = $('#bansTable');
$table.bootstrapTable({});
$table.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped'
});
this.load();
}, 500);
}
private onNavigationEnd(): void {
this.navbarService.removeNavbarLinks();
}
private async load(): Promise<void> {
const $table = $('#bansTable');
const _bans = await this.daemonService.getBans();
const bans: any[] = [];
_bans.forEach((ban) => bans.push({
'ip': ban.ip,
'host': ban.host,
'seconds': ban.seconds
}));
$table.bootstrapTable('load', bans);
}
}

View file

@ -0,0 +1,19 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { BansRoutingModule } from './bans-routing.module';
import { BansComponent } from './bans.component';
import { SharedModule } from '../shared/shared.module';
@NgModule({
declarations: [
BansComponent
],
imports: [
CommonModule,
SharedModule,
BansRoutingModule
]
})
export class BansModule { }

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { DaemonService } from './daemon.service';
describe('DaemonService', () => {
let service: DaemonService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DaemonService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,256 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { BlockCount } from '../../../../common/BlockCount';
import { firstValueFrom } from 'rxjs';
import {
GetBlockCountRequest, GetBlockHashRequest, GetBlockTemplateRequest, JsonRPCRequest,
SubmitBlockRequest, GenerateBlocksRequest, GetLastBlockHeaderRequest,
GetBlockHeaderByHashRequest, GetBlockHeaderByHeightRequest, GetBlockHeadersRangeRequest,
GetConnectionsRequest, GetInfoRequest, HardForkInfoRequest, SetBansRequest, GetBansRequest,
BannedRequest, FlushTxPoolRequest, GetOutputHistogramRequest,
SyncInfoRequest,
GetVersionRequest,
GetFeeEstimateRequest,
GetAlternateChainsRequest,
GetTxPoolBacklogRequest,
PruneBlockchainRequest,
CalculatePoWHashRequest,
FlushCacheRequest,
GetMinerDataRequest
} from '../../../../common/request';
import { BlockTemplate } from '../../../../common/BlockTemplate';
import { GeneratedBlocks } from '../../../../common/GeneratedBlocks';
import { BlockHeader } from '../../../../common/BlockHeader';
import { Connection } from '../../../../common/Connection';
import { DaemonInfo } from '../../../../common/DaemonInfo';
import { HardForkInfo } from '../../../../common/HardForkInfo';
import { Ban } from '../../../../common/Ban';
import { HistogramEntry } from '../../../../common/HistogramEntry';
import { SyncInfo } from '../../../../common/SyncInfo';
import { DaemonVersion } from '../../../../common/DaemonVersion';
import { FeeEstimate } from '../../../../common/FeeEstimate';
import { Chain } from '../../../../common/Chain';
import { RelayTxRequest } from '../../../../common/request/RelayTxRequest';
import { TxBacklogEntry } from '../../../../common/TxBacklogEntry';
import { BlockchainPruneInfo } from '../../../../common/BlockchainPruneInfo';
import { MinerData } from '../../../../common/MinerData';
@Injectable({
providedIn: 'root'
})
export class DaemonService {
private url: string = "http://127.0.0.1:28081";
private readonly headers: { [key: string]: string } = {
'Content-Type': 'application/json'
};
constructor(private httpClient: HttpClient) { }
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));
}
public async getBlockCount(): Promise<BlockCount> {
const response = await this.callJsonRpc(new GetBlockCountRequest());
return BlockCount.parse(response.result);
}
public async getBlockHash(blockHeight: number): Promise<string> {
const response = await this.callJsonRpc(new GetBlockHashRequest(blockHeight));
return response.result;
}
public async getBlockTemplate(walletAddress: string, reserveSize: number) {
const response = await this.callJsonRpc(new GetBlockTemplateRequest(walletAddress, reserveSize));
return BlockTemplate.parse(response.result);
}
public async submitBlock(... blockBlobData: string[]): Promise<void> {
const response = await this.callJsonRpc(new SubmitBlockRequest(blockBlobData));
if (response.error) {
if (!response.message) {
throw new Error(`Error code: ${response.code}`);
}
throw new Error(response.message);
}
}
public async generateBlocks(amountOfBlocks: number, walletAddress: string, prevBlock: string = '', startingNonce: number): Promise<GeneratedBlocks> {
const response = await this.callJsonRpc(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));
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));
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));
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 block_headers: any[] = response.block_headers;
const result: BlockHeader[] = [];
block_headers.forEach((block_header: any) => result.push(BlockHeader.parse(block_header)));
return result;
}
public async getConnections(): Promise<Connection[]> {
const response = await this.callJsonRpc(new GetConnectionsRequest());
const connections: any[] = response.connections;
const result: Connection[] = [];
connections.forEach((connection: any) => result.push(Connection.parse(connection)))
return result;
}
public async getInfo(): Promise<DaemonInfo> {
const response = await this.callJsonRpc(new GetInfoRequest());
return DaemonInfo.parse(response.result);
}
public async hardForkInfo(): Promise<HardForkInfo> {
const response = await this.callJsonRpc(new HardForkInfoRequest());
return HardForkInfo.parse(response.result);
}
public async setBans(...bans: Ban[]) {
const response = await this.callJsonRpc(new SetBansRequest(bans));
if (response.status != 'OK') {
throw new Error(`Error code: ${response.status}`);
}
}
public async getBans(): Promise<Ban[]> {
const response = await this.callJsonRpc(new GetBansRequest());
const bans: any[] = response.bans;
const result: Ban[] = [];
bans.forEach((ban: any) => result.push(Ban.parse(ban)));
return result;
}
public async banned(address: string): Promise<Ban> {
const response = await this.callJsonRpc(new BannedRequest(address));
const result = response.result;
if (result.status != 'OK') {
throw new Error(`Error code: ${result.response}`);
}
return new Ban(address, 0, result.banned, result.seconds);
}
public async flushTxPool(... txIds: string[]): Promise<void> {
const response = await this.callJsonRpc(new FlushTxPoolRequest(txIds));
if (response.status != 'OK') {
throw new Error(`Error code: ${response.status}`);
}
}
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 result: HistogramEntry[] = [];
entries.forEach((entry: any) => result.push(HistogramEntry.parse(entry)));
return result;
}
public async syncInfo(): Promise<SyncInfo> {
const response = await this.callJsonRpc(new SyncInfoRequest());
return SyncInfo.parse(response.result);
}
public async getVersion(): Promise<DaemonVersion> {
const response = await this.callJsonRpc(new GetVersionRequest());
return DaemonVersion.parse(response.result);
}
public async getFeeEstimate(): Promise<FeeEstimate> {
const response = await this.callJsonRpc(new GetFeeEstimateRequest());
return FeeEstimate.parse(response.result);
}
public async getAlternateChains(): Promise<Chain[]> {
const response = await this.callJsonRpc(new GetAlternateChainsRequest());
const chains: any[] = response.result.chains ? response.result.chains : [];
const result: Chain[] = [];
chains.forEach((chain: any) => result.push(Chain.parse(chain)));
return result;
}
public async relayTx(... txIds: string[]): Promise<void> {
const response = await this.callJsonRpc(new RelayTxRequest(txIds));
if (response.result.status != 'OK') {
throw new Error(`Error code: ${response.result.status}`);
}
}
public async getTxPoolBacklog(): Promise<TxBacklogEntry[]> {
const response = await this.callJsonRpc(new GetTxPoolBacklogRequest());
return TxBacklogEntry.fromBinary(response.backlog);
}
public async pruneBlockchain(check: boolean = false): Promise<BlockchainPruneInfo> {
const response = await this.callJsonRpc(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));
return response.result;
}
public async flushCache(badTxs: boolean = false, badBlocks: boolean = false): Promise<void> {
const response = await this.callJsonRpc(new FlushCacheRequest(badTxs, badBlocks));
if(response.result.status != 'OK') {
throw new Error(`Error code: ${response.result.status}`);
}
}
public async getMinerData(): Promise<MinerData> {
const response = await this.callJsonRpc(new GetMinerDataRequest());
return MinerData.parse(response.result);
}
}

View file

@ -1,7 +1,39 @@
<div class="container">
<h1 class="title">
{{ 'PAGES.DETAIL.TITLE' | translate }}
</h1>
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab" tabindex="0">
<div class="row d-flex">
<a routerLink="/">{{ 'PAGES.DETAIL.BACK_TO_HOME' | translate }}</a>
</div>
@for(card of cards; track card.header) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header">{{card.header}}</div>
<div class="card-body">
<h5 class="card-title">{{card.content}}</h5>
</div>
</div>
}
</div>
</div>
<div class="tab-pane fade" id="pills-profile" role="tabpanel" aria-labelledby="pills-profile-tab" tabindex="0">
<div class="m-3">
<table
id="table"
data-toggle="table"
data-toolbar="#toolbar"
data-height="460"
>
<thead>
<tr>
<th data-field="address">Remote Host</th>
<th data-field="peerId">Peer ID</th>
<th data-field="height">Height</th>
<th data-field="pruningSeed">Prune Seed</th>
<th data-field="state">State</th>
<th data-field="currentDownload">Download</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="tab-pane fade" id="pills-contact" role="tabpanel" aria-labelledby="pills-contact-tab" tabindex="0">Spans</div>
<div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0">...</div>
</div>

View file

@ -1,16 +1,196 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, AfterViewInit } from '@angular/core';
import { DaemonService } from '../core/services/daemon/daemon.service';
import * as jquery from 'jquery';
import * as bootstrapTable from 'bootstrap-table'
import { SyncInfo } from '../../common/SyncInfo';
import { Peer } from '../../common/Peer';
import { NavbarLink } from '../navbar/navbar.model';
import { NavbarService } from '../navbar/navbar.service';
import { NavigationEnd, Router } from '@angular/router';
import { DaemonInfo } from '../../common/DaemonInfo';
@Component({
selector: 'app-detail',
templateUrl: './detail.component.html',
styleUrls: ['./detail.component.scss']
})
export class DetailComponent implements OnInit {
export class DetailComponent implements OnInit, AfterViewInit {
constructor() { }
private syncInfo?: SyncInfo;
private daemonInfo?: DaemonInfo;
private readonly navbarLinks: NavbarLink[];
private syncStatus: string;
private height: number;
private targetHeight: number;
private nextNeededPruningSeed: number;
private overview: string;
private blockCount: number;
private version: string;
private blockchainSize: string;
private diskUsage: string;
private networkType: string;
private connectionStatus: string;
private txCount: number;
private poolSize: number;
private nodeType: string;
private syncProgress: string;
public cards: Card[];
constructor(private router: Router,private daemonService: DaemonService, private navbarService: NavbarService) {
this.syncStatus = 'Not synced';
this.height = 0;
this.targetHeight = 0;
this.nextNeededPruningSeed = 0;
this.overview = '';
this.blockCount = 0;
this.version = '';
this.diskUsage = '0 %';
this.networkType = '';
this.connectionStatus = 'offline';
this.txCount = 0;
this.poolSize = 0;
this.nodeType = 'unknown';
this.blockchainSize = '0 GB';
this.syncProgress = '0 %';
this.navbarLinks = [
new NavbarLink('pills-home-tab', '#pills-home', 'pills-home', true, 'Overview'),
new NavbarLink('pills-profile-tab', '#pills-profile', 'pills-profile', false, 'Peers')
];
this.cards = [];
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
if (event.url != '/detail') return;
this.onNavigationEnd();
}
});
}
ngOnInit(): void {
console.log('DetailComponent INIT');
}
}
ngAfterViewInit(): void {
console.log('DetailComponent AFTER VIEW INIT');
setTimeout(() => {
const $table = $('#table');
$table.bootstrapTable({});
$table.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped'
});
this.Load();
}, 500);
}
private onNavigationEnd(): void {
this.Load().then(() => {
this.cards = this.createCards();
this.navbarService.setNavbarLinks(this.navbarLinks);
});
}
private createCards(): Card[] {
return [
new Card('Connection Status', this.connectionStatus),
new Card('Network Type', this.networkType),
new Card('Node Type', this.nodeType),
new Card('Sync progress', this.syncProgress),
new Card('Scan Height', `${this.height} / ${this.targetHeight}`),
new Card('Next needed pruning seed', `${this.nextNeededPruningSeed}`),
new Card('Block count', `${this.blockCount}`),
new Card('Monero version', this.version),
new Card('Blockchain size', this.blockchainSize),
new Card('Disk usage', this.diskUsage),
new Card('Transaction count', `${this.txCount}`),
new Card('Pool size', `${this.poolSize}`)
];
}
public async Load(): Promise<void> {
const $table = $('#table');
this.syncInfo = await this.daemonService.syncInfo();
this.height = this.syncInfo.height;
this.targetHeight = this.syncInfo.targetHeight;
this.nextNeededPruningSeed = this.syncInfo.nextNeededPruningSeed;
if (this.height > 0 && this.targetHeight == 0) {
this.targetHeight = this.height;
this.syncStatus = 'Daemon synced';
}
else if (this.height > 0 && this.targetHeight > 0 && this.height == this.targetHeight) {
this.syncStatus = 'Daemon synced';
}
this.overview = this.syncInfo.overview;
const blockCount = await this.daemonService.getBlockCount();
this.blockCount = blockCount.count;
const version = await this.daemonService.getVersion();
this.version = `${version.version}`;
this.daemonInfo = await this.daemonService.getInfo();
const capacity: number = this.daemonInfo.freeSpace + this.daemonInfo.databaseSize;
const diskUsage = parseInt(`${this.daemonInfo.databaseSize * 100 / capacity}`);
const blockchainSize = (this.daemonInfo.databaseSize / 1000 / 1000 / 1000).toFixed(2);
this.blockchainSize = `${blockchainSize} GB`;
this.diskUsage = `${diskUsage} %`;
this.networkType = this.daemonInfo.nettype;
this.connectionStatus = this.daemonInfo.offline ? 'offline' : 'online';
this.txCount = this.daemonInfo.txCount;
this.poolSize = this.daemonInfo.txPoolSize;
this.version = this.daemonInfo.version;
this.syncProgress = `${(this.height*100/this.targetHeight).toFixed(2)} %`;
//const blockchainPruned = await this.isBlockchainPruned();
const blockchainPruned = false;
this.nodeType = blockchainPruned ? 'pruned' : 'full';
$table.bootstrapTable('load', this.getPeers());
}
public async isBlockchainPruned(): Promise<boolean> {
const result = await this.daemonService.pruneBlockchain(true);
return result.pruned;
}
public getPeers(): any[] {
if (!this.syncInfo) return [];
const peers: any[] = [];
this.syncInfo.peers.forEach((peer: Peer) => peers.push({
'address': peer.info.address,
'peerId': peer.info.peerId,
'height': peer.info.height,
'pruningSeed': peer.info.pruningSeed,
'state': peer.info.state,
'currentDownload': `${peer.info.currentDownload / 1000} kB/s`
}));
return peers;
}
}
class Card {
public header: string;
public content: string;
constructor(header: string, content: string) {
this.header = header;
this.content = content;
}
}

View file

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HardForkInfoComponent } from './hard-fork-info.component';
const routes: Routes = [{
path: 'hardforkinfo',
component: HardForkInfoComponent
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HardForkInfoRoutingModule { }

View file

@ -0,0 +1,10 @@
<div class="row d-flex">
@for(card of cards; track card.header) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header">{{card.header}}</div>
<div class="card-body">
<h5 class="card-title">{{card.content}}</h5>
</div>
</div>
}
</div>

View file

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

View file

@ -0,0 +1,80 @@
import { Component } from '@angular/core';
import { DaemonService } from '../core/services/daemon/daemon.service';
import { NavigationEnd, Router } from '@angular/router';
import { NavbarService } from '../navbar/navbar.service';
@Component({
selector: 'app-hard-fork-info',
templateUrl: './hard-fork-info.component.html',
styleUrl: './hard-fork-info.component.scss'
})
export class HardForkInfoComponent {
public cards: Card[];
private earliestHeight: number;
private enabled: boolean;
private threshold: number;
private blockVersion: number;
private votes: number;
private voting: number;
private window: number;
constructor(private router: Router, private daemonService: DaemonService, private navbarService: NavbarService) {
this.cards = [];
this.enabled = false;
this.earliestHeight = 0;
this.threshold = 0;
this.blockVersion = 0;
this.votes = 0;
this.voting = 0;
this.window = 0;
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
if (event.url != '/hardforkinfo') return;
this.onNavigationEnd();
}
});
}
private onNavigationEnd(): void {
this.load().then(() => {
this.cards = this.createCards();
this.navbarService.removeNavbarLinks();
});
}
private async load(): Promise<void> {
const info = await this.daemonService.hardForkInfo();
this.earliestHeight = info.earliestHeight;
this.threshold = info.threshold;
this.blockVersion = info.version;
this.votes = info.votes;
this.voting = info.voting;
this.window = info.window;
}
private createCards(): Card[] {
return [
new Card('Status', this.enabled ? 'enabled' : 'disabled'),
new Card('Earliest height', `${this.earliestHeight}`),
new Card('Threshold', `${this.threshold}`),
new Card('Block version', `${this.blockVersion}`),
new Card('Votes', `${this.votes}`),
new Card('Voting', `${this.voting}`),
new Card('Window', `${this.window}`)
]
}
}
class Card {
public header: string;
public content: string;
constructor(header: string, content: string) {
this.header = header;
this.content = content;
}
}

View file

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HardForkInfoRoutingModule } from './hard-fork-info-routing.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
HardForkInfoRoutingModule
]
})
export class HardForkInfoModule { }

View file

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { MiningComponent } from './mining.component';
const routes: Routes = [{
path: 'mining',
component: MiningComponent
}];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MiningRoutingModule { }

View file

@ -0,0 +1,53 @@
<div class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-miner-data" role="tabpanel" aria-labelledby="pills-miner-data-tab" tabindex="0">
<div class="row d-flex">
@for(card of cards; track card.header) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header">{{card.header}}</div>
<div class="card-body">
<h5 class="card-title">{{card.content}}</h5>
</div>
</div>
}
</div>
</div>
<div class="tab-pane fade" id="pills-alternate-chains" role="tabpanel" aria-labelledby="pills-alternate-chains-tab" tabindex="0">
<div class="m-3">
<table
id="chainsTable"
data-toggle="chainsTable"
data-toolbar="#toolbar"
data-height="460"
>
<thead>
<tr>
<th data-field="blockHash">Block Hash</th>
<th data-field="height">Height</th>
<th data-field="length">Length</th>
<th data-field="mainChainParentBlock">Main Chain Parent Block</th>
<th data-field="wideDifficulty">Wide Difficulty</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="tab-pane fade" id="pills-block-template" role="tabpanel" aria-labelledby="pills-block-template-tab" tabindex="0">
<div class="input-group flex-nowrap m-4">
<span class="input-group-text" id="addon-wrapping">Address</span>
<input type="text" class="form-control" aria-label="Address" aria-describedby="addon-wrapping">
</div>
<div class="input-group flex-nowrap m-4">
<span class="input-group-text" id="addon-wrapping">Reserve Size</span>
<input type="number" class="form-control" aria-label="Reserve Size" aria-describedby="addon-wrapping">
</div>
<div class="input-group m-4">
<button class="btn btn-primary" type="button" id="getBlockTemplateButton">Get Block Template</button>
</div>
</div>
<div class="tab-pane fade" id="pills-disabled" role="tabpanel" aria-labelledby="pills-disabled-tab" tabindex="0">...</div>
</div>

View file

View file

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

View file

@ -0,0 +1,132 @@
import { AfterViewInit, Component } from '@angular/core';
import { DaemonService } from '../core/services/daemon/daemon.service';
import { NavbarService } from '../navbar/navbar.service';
import { MinerData } from '../../common/MinerData';
import { NavigationEnd, Router } from '@angular/router';
import { NavbarLink } from '../navbar/navbar.model';
import { MineableTxBacklog } from '../../common/MineableTxBacklog';
import { Chain } from '../../common/Chain';
@Component({
selector: 'app-mining',
templateUrl: './mining.component.html',
styleUrl: './mining.component.scss'
})
export class MiningComponent implements AfterViewInit {
private readonly navbarLinks: NavbarLink[];
private minerData?: MinerData;
private majorVersion: number;
private height: number;
private prevId: string;
private seedHash: string;
private difficulty: number;
private medianWeight: number;
private alreadyGeneratedCoins: number;
private alternateChains: Chain[];
//private txBacklog: MineableTxBacklog[]
public cards: Card[];
constructor(private router: Router, private daemonService: DaemonService, private navbarService: NavbarService) {
this.majorVersion = 0;
this.height = 0;
this.prevId = '';
this.seedHash = '';
this.difficulty = 0;
this.medianWeight = 0;
this.alreadyGeneratedCoins = 0;
this.alternateChains = [];
this.cards = [];
this.navbarLinks = [
new NavbarLink('pills-miner-data-tab', '#pills-miner-data', 'miner-data', true, 'Miner Data'),
new NavbarLink('pills-alternate-chains-tab', '#pills-alternate-chains', 'alternate-chains', false, 'Alternate Chains'),
new NavbarLink('pills-block-template-tab', '#pills-block-template', 'block-template', false, 'Block Template')
];
this.router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
if (event.url != '/mining') return;
this.onNavigationEnd();
}
});
}
ngAfterViewInit(): void {
console.log('DetailComponent AFTER VIEW INIT');
setTimeout(() => {
const $table = $('#chainsTable');
$table.bootstrapTable({});
$table.bootstrapTable('refreshOptions', {
classes: 'table table-bordered table-hover table-dark table-striped'
});
this.load();
}, 500);
}
private onNavigationEnd(): void {
this.load().then(() => {
this.cards = this.createCards();
this.navbarService.setNavbarLinks(this.navbarLinks)
});
}
private async load(): Promise<void> {
this.minerData = await this.daemonService.getMinerData();
this.majorVersion = this.minerData.majorVersion;
this.height = this.minerData.height;
this.prevId = this.minerData.prevId;
this.seedHash = this.minerData.seedHash;
this.difficulty = this.minerData.difficulty;
this.medianWeight = this.minerData.medianWeight;
this.alreadyGeneratedCoins = this.minerData.alreadyGeneratedCoins;
this.alternateChains = await this.daemonService.getAlternateChains();
const $table = $('#chainsTable');
$table.bootstrapTable('load', this.getChains());
}
private createCards(): Card[] {
return [
new Card('Major Fork Version', `${this.majorVersion}`),
new Card('Current block height', `${this.height}`),
new Card('Previous Block Id', `${this.prevId}`),
new Card('Seed hash', `${this.seedHash}`),
new Card('Network difficulty', `${this.difficulty}`),
new Card('Median block weight', `${this.medianWeight}`),
new Card('Generated Coins', `${this.alreadyGeneratedCoins}`)
];
}
private getChains(): any[] {
const chains: any[] = [];
this.alternateChains.forEach((chain: Chain) => chains.push({
'blockHash': chain.blockHash,
'height': chain.height,
'length': chain.length,
'mainChainParentBlock': chain.mainChainParentBlock,
'wideDifficulty': chain.wideDifficulty
}))
return chains;
}
}
class Card {
public header: string;
public content: string;
constructor(header: string, content: string) {
this.header = header;
this.content = content;
}
}

View file

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MiningRoutingModule } from './mining-routing.module';
@NgModule({
declarations: [],
imports: [
CommonModule,
MiningRoutingModule
]
})
export class MiningModule { }

View file

@ -0,0 +1,28 @@
<nav class="navbar navbar-expand-lg d-flex justify-content-center py-3 text-bg-dark">
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none">
<img src="/assets/icons/monero-symbol-on-white-480.png" width="40" height="40">
<span class="fs-4">Monero Daemon</span>
</a>
<ul class="nav nav-pills" id="pills-tab" role="tablist">
@for(navbarLink of navbarLinks; track navbarLink.name) {
<li class="nav-item" role="presentation">
<button [class]="navbarLink.selected ? 'nav-link active' : 'nav-link'" [id]="navbarLink.id" data-bs-toggle="pill" [attr.data-bs-target]="navbarLink.target" type="button" role="tab" [attr.aria-controls]="navbarLink.controls" [attr.aria-selected]="navbarLink.selected">{{navbarLink.name}}</button>
</li>
}
<!--
<li class="nav-item" role="presentation">
<button class="nav-link active" id="pills-home-tab" data-bs-toggle="pill" data-bs-target="#pills-home" type="button" role="tab" aria-controls="pills-home" aria-selected="true">Overview</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-profile-tab" data-bs-toggle="pill" data-bs-target="#pills-profile" type="button" role="tab" aria-controls="pills-profile" aria-selected="false">Peers</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-contact-tab" data-bs-toggle="pill" data-bs-target="#pills-contact" type="button" role="tab" aria-controls="pills-contact" aria-selected="false">Spans</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="pills-disabled-tab" data-bs-toggle="pill" data-bs-target="#pills-disabled" type="button" role="tab" aria-controls="pills-disabled" aria-selected="false" disabled>Disabled</button>
</li>
-->
</ul>
</nav>

View file

View file

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

View file

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { NavbarService } from './navbar.service';
import { NavbarLink } from './navbar.model';
@Component({
selector: 'app-navbar',
standalone: true,
imports: [],
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss'
})
export class NavbarComponent {
public get navbarLinks(): NavbarLink[] {
return this.navbarService.navbarLinks;
}
constructor(private navbarService: NavbarService) {
}
}

View file

@ -0,0 +1,18 @@
export class NavbarLink {
public id: string;
public target: string;
public controls: string;
public selected: boolean;
public name: string;
constructor(id: string, target: string, controls: string, selected: boolean, name: string) {
this.id = id;
this.target = target;
this.controls = controls;
this.selected = selected;
this.name = name;
}
}

View file

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { NavbarService } from './navbar.service';
describe('NavbarService', () => {
let service: NavbarService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NavbarService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View file

@ -0,0 +1,28 @@
import { Injectable } from '@angular/core';
import { NavbarLink } from './navbar.model';
@Injectable({
providedIn: 'root'
})
export class NavbarService {
private _navbarLinks: NavbarLink[] = [];
public get navbarLinks(): NavbarLink[] {
return this._navbarLinks;
}
constructor() { }
public addNavbarLink(... navbarLinks: NavbarLink[]): void {
navbarLinks.forEach((navLink: NavbarLink) => this._navbarLinks.push(navLink));
}
public setNavbarLinks(navbarLinks: NavbarLink[]): void {
this._navbarLinks = navbarLinks;
}
public removeNavbarLinks(): void {
this.setNavbarLinks([]);
}
}

View file

@ -0,0 +1,12 @@
<div class="d-flex flex-column flex-shrink-0 p-3 text-bg-dark" style="width: 280px;">
<ul class="nav nav-pills flex-column mb-auto">
@for (navLink of navLinks; track navLink.title) {
<a routerLink="{{navLink.path}}" [ngClass]="isActive(navLink) ? 'nav-link active' : 'nav-link text-white'">
<i [class]="navLink.icon"></i>
{{navLink.title}}
</a>
}
</ul>
<hr>
</div>

View file

View file

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

View file

@ -0,0 +1,44 @@
import { CommonModule, NgClass, NgFor } from '@angular/common';
import { Component } from '@angular/core';
import { ChildActivationEnd, ChildActivationStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterEvent, RouterModule, RoutesRecognized } from '@angular/router';
@Component({
selector: 'app-sidebar',
standalone: true,
templateUrl: './sidebar.component.html',
styleUrl: './sidebar.component.scss',
imports: [RouterModule, NgClass]
})
export class SidebarComponent {
public readonly navLinks: NavLink[];
public isLoading: boolean;
public errorMessage: string;
constructor(private router: Router) {
this.navLinks = [
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
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'),
];
this.isLoading = false;
this.errorMessage = '';
}
public isActive(navLink: NavLink): boolean {
return navLink.path == this.router.url;
}
}
class NavLink {
public readonly title: string;
public readonly path: string;
public readonly icon: string;
constructor(title: string, path: string, icon: string) {
this.title = title;
this.path = path;
this.icon = icon;
}
}

View file

@ -5,7 +5,7 @@
"GO_TO_DETAIL": "Go to Detail"
},
"DETAIL": {
"TITLE": "Detail page !",
"TITLE": "Monero Server Manager: Dashboard",
"BACK_TO_HOME": "Back to Home"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

30
src/common/Ban.ts Normal file
View file

@ -0,0 +1,30 @@
export class Ban {
public readonly host: string;
public readonly ip: number;
public readonly ban: boolean;
public readonly seconds: number;
constructor(host: string, ip: number, ban: boolean, seconds: number) {
this.host = host;
this.ip = ip;
this.ban = ban;
this.seconds = seconds;
}
public toDictionary(): { [key: string]: any} {
return {
'host': this.host,
'ip': this.ip,
'ban': this.ban,
'seconds': this.seconds
}
}
public static parse(ban: any): Ban {
const host = ban.host;
const ip = ban.ip;
const seconds = ban.seconds;
return new Ban(host, ip, true, seconds);
}
}

29
src/common/BlockCount.ts Normal file
View file

@ -0,0 +1,29 @@
export class BlockCount {
public count: number;
public status: string;
public untrusted: boolean;
constructor(count: number, status: string, untrusted: boolean) {
this.count = count;
this.status = status;
this.untrusted = untrusted;
}
public static parse(blockCount: any): BlockCount {
if (blockCount == null) {
throw new Error("Cannot parse null value");
}
const count: number = parseInt(blockCount.count);
if (typeof blockCount.status != "string") {
throw new Error("Invalid block count status");
}
const status: string = blockCount.status;
const untrusted: boolean = blockCount.untrusted == true ? true : false;
return new BlockCount(count, status, untrusted);
}
}

106
src/common/BlockHeader.ts Normal file
View file

@ -0,0 +1,106 @@
/**
* {
"block_size": 5500,
"block_weight": 5500,
"cumulative_difficulty": 86164894009456483,
"cumulative_difficulty_top64": 0,
"depth": 0,
"difficulty": 227026389695,
"difficulty_top64": 0,
"hash": "a6ad87cf357a1aac1ee1d7cb0afa4c2e653b0b1ab7d5bf6af310333e43c59dd0",
"height": 2286454,
"long_term_weight": 5500,
"major_version": 14,
"miner_tx_hash": "a474f87de1645ff14c5e90c477b07f9bc86a22fb42909caa0705239298da96d0",
"minor_version": 14,
"nonce": 249602367,
"num_txes": 3,
"orphan_status": false,
"pow_hash": "",
"prev_hash": "fa17fefe1d05da775a61a3dc33d9e199d12af167ef0ab37e52b51e8487b50f25",
"reward": 1181337498013,
"timestamp": 1612088597,
"wide_cumulative_difficulty": "0x1321e83bb8af763",
"wide_difficulty": "0x34dbd3cabf"
},
*/
import { ThisReceiver } from "@angular/compiler";
export class BlockHeader {
public readonly blockSize: number;
public readonly blockWeight: number;
public readonly cumulativeDifficulty: number;
public readonly cumulativeDifficultyTop64: number;
public readonly depth: number;
public readonly difficulty: number;
public readonly difficultyTop64: number;
public readonly hash: string;
public readonly height: number;
public readonly longTermWeight: number;
public readonly majorVersion: number;
public readonly minerTxHash: string;
public readonly minorVersion: number;
public readonly nonce: number;
public readonly numTxes: number;
public readonly orphanStatus: boolean;
public readonly powHash: string;
public readonly prevHash: string;
public readonly reward: number;
public readonly timestamp: number;
public readonly wideCumulativeDifficulty: string;
public readonly wideDifficulty: string;
constructor(blockSize: number, blockWeight: number, cumulativeDifficulty: number, cumulativeDifficultyTop64: number, depth: number, difficulty: number, difficultyTop64: number, hash: string, height: number, longTermWeight: number, majorVersion: number
, minerTxHash: string, minorVersion: number, nonce: number, numTxes: number, orphanStatus: boolean, powHash: string, prevHash: string, reward: number, timestamp: number, wideCumulativeDifficulty: string, wideDifficulty: string
) {
this.blockSize = blockSize;
this.blockWeight = blockWeight;
this.cumulativeDifficulty = cumulativeDifficulty;
this.cumulativeDifficultyTop64 = cumulativeDifficultyTop64;
this.depth = depth;
this.difficulty = difficulty;
this.difficultyTop64 = difficultyTop64;
this.hash = hash;
this.height = height;
this.longTermWeight = longTermWeight;
this.majorVersion = majorVersion;
this.minerTxHash = minerTxHash;
this.minorVersion = minorVersion;
this.nonce = nonce;
this.numTxes = numTxes;
this.orphanStatus = orphanStatus;
this.powHash = powHash;
this.prevHash = prevHash;
this.reward = reward;
this.timestamp = timestamp;
this.wideCumulativeDifficulty = wideCumulativeDifficulty;
this.wideDifficulty = wideDifficulty;
}
public static parse(blockHeader: any): BlockHeader {
const blockSize = blockHeader.block_size;
const blockWeight = blockHeader.block_weight;
const cumulativeDifficulty = blockHeader.cumulative_difficulty;
const cumulativeDifficultyTop64 = blockHeader.cumulative_difficulty_top64;
const depth = blockHeader.depth;
const difficulty = blockHeader.difficulty;
const difficultyTop64 = blockHeader.difficulty_top64;
const hash = blockHeader.hash;
const height = blockHeader.height;
const longTermWeight = blockHeader.long_term_weight;
const majorVersion = blockHeader.major_version;
const minerTxHash = blockHeader.miner_tx_hash;
const minorVersion = blockHeader.minor_version;
const nonce = blockHeader.nonce;
const numTxes = blockHeader.num_txes;
const orphanStatus = blockHeader.orphan_status;
const powHash = blockHeader.pow_hash;
const prevHash = blockHeader.prev_hash;
const reward = blockHeader.reward;
const timestamp = blockHeader.timestamp;
const wideCumulativeDifficulty = blockHeader.wide_cumulative_difficulty;
const wideDifficulty = blockHeader.wide_difficulty;
return new BlockHeader(blockSize, blockWeight, cumulativeDifficulty, cumulativeDifficultyTop64, depth, difficulty, difficultyTop64, hash, height, longTermWeight, majorVersion, minerTxHash, minorVersion, nonce, numTxes, orphanStatus, powHash, prevHash, reward, timestamp, wideCumulativeDifficulty, wideDifficulty);
}
}

View file

@ -0,0 +1,51 @@
export class BlockTemplate {
public readonly blockHashingBlob: string;
public readonly blockTemplateBlob: string;
public readonly difficulty: number;
public readonly difficultyTop64: number;
public readonly expectedReward: number;
public readonly height: number;
public readonly nextSeedHash: string;
public readonly prevHash: string;
public readonly reservedOffset: number;
public readonly seedHash: string;
public readonly seedHeight: number;
public readonly status: string;
public readonly untrusted: boolean;
public readonly wideDifficulty: string;
constructor(blockHashingBlob: string, blockTemplateBlob: string, difficulty: number, difficultyTop64: number, expectedReward: number, height: number, nextSeedHash: string, prevHash: string, reservedOffset: number, seedHash: string, seedHeight: number, status: string, untrusted: boolean, wideDifficulty: string) {
this.blockHashingBlob = blockHashingBlob;
this.blockTemplateBlob = blockTemplateBlob;
this.difficulty = difficulty;
this.difficultyTop64 = difficultyTop64;
this.expectedReward = expectedReward;
this.height = height;
this.nextSeedHash = nextSeedHash;
this.prevHash = prevHash;
this.reservedOffset = reservedOffset;
this.seedHash = seedHash;
this.seedHeight = seedHeight;
this.status = status;
this.untrusted = untrusted;
this.wideDifficulty = wideDifficulty;
}
public static parse(blockTemplate: any): BlockTemplate {
const blockHashingBlob = blockTemplate.blockhashing_blob;
const blockTemplateBlob = blockTemplate.blocktemplate_blob;
const difficulty = blockTemplate.difficulty;
const difficultyTop64 = blockTemplate.difficulty_top64;
const expectedReward = blockTemplate.expected_reward;
const height = blockTemplate.height;
const nextSeedHash = blockTemplate.next_seed_hash;
const prevHash = blockTemplate.prev_hash;
const reservedOffset = blockTemplate.reserved_offset;
const seedHash = blockTemplate.seed_hash;
const seedHeight = blockTemplate.seed_height;
const status = blockTemplate.status;
const untrusted = blockTemplate.untrusted;
const wideDifficulty = blockTemplate.wide_difficulty;
return new BlockTemplate(blockHashingBlob, blockTemplateBlob, difficulty, difficultyTop64, expectedReward, height, nextSeedHash, prevHash, reservedOffset, seedHash, seedHeight, status, untrusted, wideDifficulty);
}
}

View file

@ -0,0 +1,16 @@
export class BlockchainPruneInfo {
public readonly pruned: boolean;
public readonly pruningSeed: number;
constructor(pruned: boolean, pruningSeed: number) {
this.pruned = pruned;
this.pruningSeed = pruningSeed;
}
public static parse(info: any): BlockchainPruneInfo {
const pruned = info.pruned;
const pruningSeed = info.pruning_seed;
return new BlockchainPruneInfo(pruned, pruningSeed);
}
}

34
src/common/Chain.ts Normal file
View file

@ -0,0 +1,34 @@
export class Chain {
public readonly blockHash: string;
public readonly blockHashes: string[];
public readonly difficulty: number;
public readonly difficultyTop64: number;
public readonly height: number;
public readonly length: number;
public readonly mainChainParentBlock: string;
public readonly wideDifficulty: string;
constructor(blockHash: string, blockHashes: string[], difficulty: number, difficultyTop64: number, height: number, length: number, mainChainParentBlock: string, wideDifficulty: string) {
this.blockHash = blockHash;
this.blockHashes = blockHashes;
this.difficulty = difficulty;
this.difficultyTop64 = difficultyTop64;
this.height = height;
this.length = length;
this.mainChainParentBlock = mainChainParentBlock;
this.wideDifficulty = wideDifficulty;
}
public static parse(chain: any): Chain {
const blockHash = chain.block_hash;
const blockHashes = chain.block_hashes;
const difficulty = chain.difficulty;
const difficultyTop64 = chain.difficulty_top64;
const height = chain.height;
const length = chain.length;
const mainChainParentBlock = chain.main_chain_parent_block;
const wideDifficulty = chain.wide_difficulty;
return new Chain(blockHash, blockHashes, difficulty, difficultyTop64, height, length, mainChainParentBlock, wideDifficulty);
}
}

102
src/common/Connection.ts Normal file
View file

@ -0,0 +1,102 @@
/**
*
address - string; The peer's address, actually IPv4 & port
avg_download - unsigned int; Average bytes of data downloaded by node.
avg_upload - unsigned int; Average bytes of data uploaded by node.
connection_id - string; The connection ID
current_download - unsigned int; Current bytes downloaded by node.
current_upload - unsigned int; Current bytes uploaded by node.
height- unsigned int; The peer height
host - string; The peer host
incoming - boolean; Is the node getting information from your node?
ip - string; The node's IP address.
live_time - unsigned int
local_ip - boolean
localhost - boolean
peer_id - string; The node's ID on the network.
port - string; The port that the node is using to connect to the network.
recv_count - unsigned int
recv_idle_time - unsigned int
send_count - unsigned int
send_idle_time - unsigned int
state - string
support_flags - unsigned int
*/
export class Connection {
public readonly address: string;
public readonly avgDownload: number;
public readonly avgUpload: number;
public readonly connectionId: string;
public readonly currentDownload: number;
public readonly currentUpload: number;
public readonly height: number;
public readonly host: number;
public readonly incoming: boolean;
public readonly ip: string;
public readonly liveTime: number;
public readonly localIp: boolean;
public readonly localhost: boolean;
public readonly peerId: string;
public readonly port: string;
public readonly pruningSeed: number;
public readonly recvCount: number;
public readonly recvIdleTime: number;
public readonly sendCount: number;
public readonly sendIdleTime: number;
public readonly state: string;
public readonly supportFlags: number;
constructor(address: string, avgDownload: number, avgUpload: number, connectionId: string, currentDownload: number, currentUpload: number, height: number, host: number, incoming: boolean, ip: string, liveTime: number, localIp: boolean, localhost: boolean, peerId: string, port: string, pruningSeed: number, recvCount: number, recvIdleTime: number, sendCount: number, sendIdleTime: number, state: string, supportFlags: number) {
this.address = address;
this.avgDownload = avgDownload;
this.avgUpload = avgUpload;
this.connectionId = connectionId;
this.currentDownload = currentDownload;
this.currentUpload = currentUpload;
this.height = height;
this.host = host;
this.incoming = incoming;
this.ip = ip;
this.liveTime = liveTime;
this.localIp = localIp;
this.localhost = localhost;
this.peerId = peerId;
this.port = port;
this.pruningSeed = pruningSeed
this.recvCount = recvCount;
this.recvIdleTime = recvIdleTime;
this.sendCount = sendCount;
this.sendIdleTime = sendIdleTime;
this.state = state;
this.supportFlags = supportFlags;
}
public static parse(connection: any): Connection {
const address = connection.address;
const avgDownload = connection.avg_download;
const avgUpload = connection.avg_upload;
const connectionId = connection.connection_id;
const currentDownload = connection.current_download;
const currentUpload = connection.current_upload;
const height = connection.height;
const host = connection.host;
const incoming = connection.incoming;
const ip = connection.ip;
const liveTime = connection.live_time;
const localIp = connection.local_ip;
const localhost = connection.localhost;
const peerId = connection.peer_id;
const port = connection.port;
const pruningSeed = connection.pruning_seed;
const recvCount = connection.recv_count;
const recvIdleTime = connection.recv_idle_time;
const sendCount = connection.send_count;
const sendIdleTime = connection.send_idle_time;
const state = connection.state;
const supportFlags = connection.support_flags;
return new Connection(address, avgDownload, avgUpload, connectionId, currentDownload, currentUpload, height, host, incoming, ip, liveTime, localIp, localhost, peerId, port, pruningSeed, recvCount, recvIdleTime, sendCount, sendIdleTime, state, supportFlags);
}
}

184
src/common/DaemonInfo.ts Normal file
View file

@ -0,0 +1,184 @@
export class DaemonInfo {
public readonly adjustedTime: number;
public readonly altBlocksCount: number;
public readonly blockSizeLimit: number;
public readonly blockSizeMedian: number;
public readonly bootstrapDaemonAddress: string;
public readonly busySyncing: boolean;
public readonly credits: number;
public readonly cumulativeDifficulty: number;
public readonly cumulativeDifficultyTop64: number;
public readonly databaseSize: number;
public readonly difficulty: number;
public readonly difficultyTop64: number;
public readonly freeSpace: number;
public readonly greyPeerlistSize: number;
public readonly height: number;
public readonly heightWithoutBootstrap: number;
public readonly incomingConnectionsCount: number;
public readonly mainnet: boolean;
public readonly nettype: string;
public readonly offline: boolean;
public readonly outgoingConnectionsCount: number;
public readonly rpcConnectionsCount: number;
public readonly stagenet: boolean;
public readonly startTime: number;
public readonly status: string;
public readonly synchronized: boolean;
public readonly target: number;
public readonly targetHeight: number;
public readonly testnet: boolean;
public readonly topBlockHash: string;
public readonly topHash: string;
public readonly txCount: number;
public readonly txPoolSize: number;
public readonly untrusted: boolean;
public readonly updateAvailable: boolean;
public readonly version: string;
public readonly wasBoostrapEverUsed: boolean;
public readonly whitePeerlistSize: number;
public readonly wideCumulativeDifficulty: string;
public readonly wideDifficulty: string;
constructor(
adjustedTime: number,
altBlocksCount: number,
blockSizeLimit: number,
blockSizeMedian: number,
bootstrapDaemonAddress: string,
busySyncing: boolean,
credits: number,
cumulativeDifficulty: number,
cumulativeDifficultyTop64: number,
databaseSize: number,
difficulty: number,
difficultyTop64: number,
freeSpace: number,
greyPeerlistSize: number,
height: number,
heightWithoutBootstrap: number,
incomingConnectionsCount: number,
mainnet: boolean,
nettype: string,
offline: boolean,
outgoingConnectionsCount: number,
rpcConnectionsCount: number,
stagenet: boolean,
startTime: number,
status: string,
synchronized: boolean,
target: number,
targetHeight: number,
testnet: boolean,
topBlockHash: string,
topHash: string,
txCount: number,
txPoolSize: number,
untrusted: boolean,
updateAvailable: boolean,
version: string,
wasBoostrapEverUsed: boolean,
whitePeerlistSize: number,
wideCumulativeDifficulty: string,
wideDifficulty: string
) {
this.adjustedTime = adjustedTime;
this.altBlocksCount = altBlocksCount;
this.blockSizeLimit = blockSizeLimit;
this.blockSizeMedian = blockSizeMedian;
this.bootstrapDaemonAddress = bootstrapDaemonAddress;
this.busySyncing = busySyncing;
this.credits = credits;
this.cumulativeDifficulty = cumulativeDifficulty;
this.cumulativeDifficultyTop64 = cumulativeDifficultyTop64;
this.databaseSize = databaseSize;
this.difficulty = difficulty;
this.difficultyTop64 = difficultyTop64;
this.freeSpace = freeSpace;
this.greyPeerlistSize = greyPeerlistSize;
this.height = height;
this.heightWithoutBootstrap = heightWithoutBootstrap;
this.incomingConnectionsCount = incomingConnectionsCount;
this.mainnet = mainnet;
this.nettype = nettype;
this.offline = offline;
this.outgoingConnectionsCount = outgoingConnectionsCount;
this.rpcConnectionsCount = rpcConnectionsCount;
this.stagenet = stagenet;
this.startTime = startTime;
this.status = status;
this.synchronized = synchronized;
this.target = target;
this.targetHeight = targetHeight;
this.testnet = testnet;
this.topBlockHash = topBlockHash;
this.topHash = topHash;
this.txCount = txCount;
this.txPoolSize = txPoolSize;
this.untrusted = untrusted;
this.updateAvailable = updateAvailable;
this.version = version;
this.wasBoostrapEverUsed = wasBoostrapEverUsed;
this.whitePeerlistSize = whitePeerlistSize;
this.wideCumulativeDifficulty = wideCumulativeDifficulty;
this.wideDifficulty = wideDifficulty;
}
public static parse(info: any): DaemonInfo {
const adjustedTime = info.adjusted_time;
const altBlocksCount = info.alt_blocks_count;
const blockSizeLimit = info.block_size_limit;
const blockSizeMedian = info.block_size_median;
const bootstrapDaemonAddress = info.bootstrap_daemon_address;
const busySyncing = info.busy_syncing;
const credits = info.credits;
const cumulativeDifficulty = info.cumulative_difficulty;
const cumulativeDifficultyTop64 = info.cumulative_difficulty_top64;
const databaseSize = info.database_size;
const difficulty = info.difficulty;
const difficultyTop64 = info.difficulty_top64;
const freeSpace = info.free_space;
const greyPeerlistSize = info.grey_peerlist_size;
const height = info.height;
const heightWithoutBootstrap = info.height_without_bootstrap;
const incomingConnectionsCount = info.incoming_connections_count;
const mainnet = info.mainnet;
const nettype = info.nettype;
const offline = info.offline;
const outgoingConnectionsCount = info.outgoing_connections_count;
const rpcConnectionsCount = info.rpc_connections_count;
const stagenet = info.stagenet;
const startTime = info.start_time;
const status = info.status;
const synchronized = info.synchronized;
const target = info.target;
const targetHeight = info.target_height;
const testnet = info.testnet;
const topBlockHash = info.top_block_hash;
const topHash = info.top_hash;
const txCount = info.tx_count;
const txPoolSize = info.tx_pool_size;
const untrusted = info.untrusted;
const updateAvailable = info.update_available;
const version = info.version;
const wasBoostrapEverUsed = info.was_boostrap_ever_used;
const whitePeerlistSize = info.white_peerlist_size;
const wideCumulativeDifficulty = info.wide_cumulative_difficulty;
const wideDifficulty = info.wide_difficulty;
return new DaemonInfo(
adjustedTime, altBlocksCount, blockSizeLimit, blockSizeMedian,
bootstrapDaemonAddress, busySyncing, credits, cumulativeDifficulty,
cumulativeDifficultyTop64, databaseSize, difficulty,
difficultyTop64, freeSpace, greyPeerlistSize,
height, heightWithoutBootstrap, incomingConnectionsCount, mainnet,
nettype, offline, outgoingConnectionsCount, rpcConnectionsCount,
stagenet, startTime, status, synchronized, target, targetHeight,
testnet, topBlockHash, topHash, txCount, txPoolSize, untrusted,
updateAvailable, version, wasBoostrapEverUsed, whitePeerlistSize,
wideCumulativeDifficulty, wideDifficulty
);
}
}

View file

@ -0,0 +1,16 @@
export class DaemonVersion {
public readonly version: number;
public readonly release: boolean;
constructor(version: number, release: boolean) {
this.version = version;
this.release = release;
}
public static parse(version: any) {
const v: number = version.version;
const release: boolean = version.release;
return new DaemonVersion(v, release);
}
}

19
src/common/FeeEstimate.ts Normal file
View file

@ -0,0 +1,19 @@
export class FeeEstimate {
public readonly fee: number;
public readonly fees: number[];
public readonly quantizationMask: number;
constructor(fee: number, fees: number[], quantizationMask: number) {
this.fee = fee;
this.fees = fees;
this.quantizationMask = quantizationMask;
}
public static parse(estimate: any): FeeEstimate {
const fee = estimate.fee;
const fees = estimate.fees;
const quantizationMask = estimate.quantization_mask;
return new FeeEstimate(fee, fees, quantizationMask);
}
}

View file

@ -0,0 +1,22 @@
export class GeneratedBlocks {
public readonly blocks: string[];
public readonly height: number;
public readonly status: string;
public readonly untrusted: boolean;
constructor(blocks: string[], height: number, status: string, untrusted: boolean) {
this.blocks = blocks;
this.height = height;
this.status = status;
this.untrusted = untrusted;
}
public static parse(generatedBlocks: any): GeneratedBlocks {
const blocks: string[] = generatedBlocks.blocks;
const height: number = generatedBlocks.height;
const status: string = generatedBlocks.status;
const untrusted: boolean = generatedBlocks.untrusted;
return new GeneratedBlocks(blocks, height, status, untrusted);
}
}

View file

@ -0,0 +1,37 @@
export class HardForkInfo {
public readonly earliestHeight: number;
public readonly enabled: boolean;
public readonly state: number;
public readonly threshold: number;
public readonly topHash: string;
public readonly version: number;
public readonly votes: number;
public readonly voting: number;
public readonly window: number;
constructor(earliestHeight: number, enabled: boolean, state: number, threshold: number, topHash: string, version: number, votes: number, voting: number, window: number) {
this.earliestHeight = earliestHeight;
this.enabled = enabled;
this.state = state;
this.threshold = threshold;
this.topHash = topHash;
this.version = version;
this.votes = votes;
this.voting = voting;
this.window = window;
}
public static parse(hardForkInfo: any): HardForkInfo {
const earliestHeight = hardForkInfo.earliest_height;
const enabled = hardForkInfo.enabled;
const state = hardForkInfo.state;
const threshold = hardForkInfo.threshold;
const topHash = hardForkInfo.top_hash;
const version = hardForkInfo.version;
const votes = hardForkInfo.votes;
const voting = hardForkInfo.voting;
const window = hardForkInfo.window;
return new HardForkInfo(earliestHeight, enabled, state, threshold, topHash, version, votes, voting, window);
}
}

View file

@ -0,0 +1,22 @@
export class HistogramEntry {
public readonly amount: number;
public readonly totalInstances: number;
public readonly unlockedInstances: number;
public readonly recentInstances: number;
constructor(amount: number, totalInstances: number, unlockedInstances: number, recentInstances: number) {
this.amount = amount;
this.totalInstances = totalInstances;
this.unlockedInstances = unlockedInstances;
this.recentInstances = recentInstances;
}
public static parse(entry: any) {
const amount = entry.amount;
const totalInstances = entry.total_instances;
const unlockedInstances = entry.unlocked_instances;
const recentInstances = entry.recent_instances;
return new HistogramEntry(amount, totalInstances, unlockedInstances, recentInstances);
}
}

View file

@ -0,0 +1,19 @@
export class MineableTxBacklog {
public readonly id: string;
public readonly weight: number;
public readonly fee: number;
constructor(id: string, weight: number, fee: number) {
this.id = id;
this.weight = weight;
this.fee = fee;
}
public static parse(txBacklog: any): MineableTxBacklog {
const id: string = txBacklog.id;
const weight: number = txBacklog.weight;
const fee: number = txBacklog.fee;
return new MineableTxBacklog(id, weight, fee);
}
}

36
src/common/MinerData.ts Normal file
View file

@ -0,0 +1,36 @@
import { MineableTxBacklog } from "./MineableTxBacklog";
export class MinerData {
public readonly majorVersion: number;
public readonly height: number;
public readonly prevId: string;
public readonly seedHash: string;
public readonly difficulty: number;
public readonly medianWeight: number;
public readonly alreadyGeneratedCoins: number;
public readonly txBacklog: MineableTxBacklog[];
constructor(majorVersion: number, height: number, prevId: string, seedHash: string, difficulty: number, medianWeight: number, alreadyGeneratedCoins: number, txBacklog: MineableTxBacklog[]) {
this.majorVersion = majorVersion;
this.height = height;
this.prevId = prevId;
this.seedHash = seedHash;
this.difficulty = difficulty;
this.medianWeight = medianWeight;
this.alreadyGeneratedCoins = alreadyGeneratedCoins;
this.txBacklog = txBacklog;
}
public static parse(minerData: any): MinerData {
const majorVersion = minerData.major_version;
const height = minerData.height;
const prevId = minerData.prev_id;
const seedHash = minerData.seed_hash;
const difficulty = minerData.difficulty;
const medianWeight = minerData.median_weight;
const alreadyGeneratedCoins = minerData.already_generated_coins;
const txBacklog = minerData.tx_backlog;
return new MinerData(majorVersion, height, prevId, seedHash, difficulty, medianWeight, alreadyGeneratedCoins, txBacklog);
}
}

14
src/common/Peer.ts Normal file
View file

@ -0,0 +1,14 @@
import { Connection } from "./Connection";
export class Peer {
public readonly info: Connection;
constructor(info: Connection) {
this.info = info;
}
public static parse(peer: any): Peer {
const info: any = peer.info;
return new Peer(Connection.parse(info));
}
}

43
src/common/Span.ts Normal file
View file

@ -0,0 +1,43 @@
/**
*
connection_id - string; Id of connection
nblocks - unsigned int; number of blocks in that span
rate - unsigned int; connection rate
remote_address - string; peer address the node is downloading (or has downloaded) than span from
size - unsigned int; total number of bytes in that span's blocks (including txes)
speed - unsigned int; connection speed
start_block_height - unsigned int; block height of the first block in that span
*/
export class Span {
public readonly connectionId: string;
public readonly nBlocks: number;
public readonly rate: number;
public readonly remoteAddress: string;
public readonly size: number;
public readonly speed: number;
public readonly startBlockHeight: number;
constructor(connectionId: string, nBlocks: number, rate: number, remoteAddress: string, size: number, speed: number, startBlockHeight: number) {
this.connectionId = connectionId;
this.nBlocks = nBlocks;
this.rate = rate;
this.remoteAddress = remoteAddress;
this.size = size;
this.speed = speed;
this.startBlockHeight = startBlockHeight;
}
public static parse(span: any): Span {
const connectionId: string = span.connection_id;
const nBlocks: number = span.nblocks;
const rate: number = span.rate;
const remoteAddress: string = span.remote_address;
const size: number = span.size;
const speed: number = span.speed;
const startBlockHeight = span.start_block_height;
return new Span(connectionId, nBlocks, rate, remoteAddress, size, speed, startBlockHeight);
}
}

37
src/common/SyncInfo.ts Normal file
View file

@ -0,0 +1,37 @@
import { Peer } from "./Peer";
import { Span } from "./Span";
export class SyncInfo {
public readonly height: number;
public readonly targetHeight: number;
public readonly nextNeededPruningSeed: number;
public readonly overview: string;
public readonly peers: Peer[];
public readonly spans: Span[];
constructor(height: number, targetHeight: number, nextNeededPruningSeed: number, overview: string, peers: Peer[], spans: Span[]) {
this.height = height;
this.targetHeight = targetHeight;
this.nextNeededPruningSeed = nextNeededPruningSeed;
this.overview = overview;
this.peers = peers;
this.spans = spans;
}
public static parse(syncInfo: any): SyncInfo {
const height = syncInfo.height;
const targetHeight = syncInfo.target_height;
const nextNeededPruningSeed = syncInfo.next_needed_pruning_seed;
const overview = syncInfo.overview;
const peers: Peer[] = [];
const spans: Span[] = [];
const rawPeers: any[] = syncInfo.peers;
const rawSpans: any[] | undefined = syncInfo.rawSpans;
rawPeers.forEach((peer: any) => peers.push(Peer.parse(peer)));
if (rawSpans) rawSpans.forEach((span: any) => spans.push(Span.parse(span)));
return new SyncInfo(height, targetHeight, nextNeededPruningSeed, overview, peers, spans);
}
}

View file

@ -0,0 +1,15 @@
export class TxBacklogEntry {
public readonly blobSize: number;
public readonly fee: number;
public readonly timeInPool: number;
constructor(blobSize: number, fee: number, timeInPool: number) {
this.blobSize = blobSize;
this.fee = fee;
this.timeInPool = timeInPool;
}
public static fromBinary(binary: string): TxBacklogEntry[] {
throw new Error("TxBacklogEntry.fromBinary(): not implemented");
}
}

28
src/common/request.ts Normal file
View file

@ -0,0 +1,28 @@
export { JsonRPCRequest } from "./request/JsonRPCRequest";
export { GetBlockCountRequest } from "./request/GetBlockCountRequest";
export { GetBlockHashRequest } from "./request/GetBlockHashRequest";
export { GetBlockTemplateRequest } from "./request/GetBlockTemplateRequest";
export { SubmitBlockRequest } from "./request/SubmitBlockRequest";
export { GenerateBlocksRequest } from "./request/GenerateBlocksRequest";
export { GetLastBlockHeaderRequest } from "./request/GetLastBlockHeaderRequest";
export { GetBlockHeaderByHashRequest } from "./request/GetBlockHeaderByHashRequest";
export { GetBlockHeaderByHeightRequest } from "./request/GetBlockHeaderByHeightRequest";
export { GetBlockHeadersRangeRequest } from "./request/GetBlockHeadersRangeRequest";
export { GetConnectionsRequest } from "./request/GetConnectionsRequest";
export { GetInfoRequest } from "./request/GetInfoRequest";
export { HardForkInfoRequest } from "./request/HardForkInfoRequest";
export { SetBansRequest } from "./request/SetBansRequest";
export { GetBansRequest } from "./request/GetBansRequest";
export { BannedRequest } from "./request/BannedRequest";
export { FlushTxPoolRequest } from "./request/FlushTxPoolRequest";
export { GetOutputHistogramRequest } from "./request/GetOutputHistogramRequest";
export { SyncInfoRequest } from "./request/SyncInfoRequest";
export { GetVersionRequest } from "./request/GetVersionRequest";
export { GetFeeEstimateRequest } from "./request/GetFeeEstimateRequest";
export { GetAlternateChainsRequest } from "./request/GetAlternateChainsRequest";
export { RelayTxRequest } from "./request/RelayTxRequest";
export { GetTxPoolBacklogRequest } from "./request/GetTxPoolBacklogRequest";
export { PruneBlockchainRequest } from "./request/PruneBlockchainRequest";
export { CalculatePoWHashRequest } from "./request/CalculatePoWHashRequest";
export { FlushCacheRequest } from "./request/FlushCacheRequest";
export { GetMinerDataRequest } from "./request/GetMinerDataRequest";

View file

@ -0,0 +1,21 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class BannedRequest extends JsonRPCRequest {
public override readonly method: string = 'banned';
public readonly address: string;
constructor(address: string) {
super();
this.address = address;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'address': this.address
};
return dict;
}
}

View file

@ -0,0 +1,31 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class CalculatePoWHashRequest extends JsonRPCRequest {
public override readonly method: string = 'calc_pow';
public readonly majorVersion: number;
public readonly height: number;
public readonly blockBlob: string;
public readonly seedHash: string;
constructor(majorVersion: number, height: number, blockBlob: string, seedHash: string) {
super();
this.majorVersion = majorVersion;
this.height = height;
this.blockBlob = blockBlob;
this.seedHash = seedHash;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'major_version': this.majorVersion,
'height': this.height,
'blockBlob': this.blockBlob,
'seedHash': this.seedHash
}
return dict;
}
}

View file

@ -0,0 +1,24 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class FlushCacheRequest extends JsonRPCRequest {
public override readonly method: string = 'flush_cache';
public readonly badTxs: boolean;
public readonly badBlocks: boolean;
constructor(badTxs: boolean = false, badBlocks: boolean = false) {
super();
this.badTxs = badTxs;
this.badBlocks = badBlocks;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'bad_txs': this.badTxs,
'bad_blocks': this.badBlocks
};
return dict;
}
}

View file

@ -0,0 +1,21 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class FlushTxPoolRequest extends JsonRPCRequest {
public override readonly method: string = 'flush_txpool';
public txIds: string[];
constructor(txIds: string[]) {
super();
this.txIds = txIds;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'txids': this.txIds
}
return dict;
}
}

View file

@ -0,0 +1,35 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GenerateBlocksRequest extends JsonRPCRequest {
public override method: string = "generateblocks";
public readonly amountOfBlocks: number;
public readonly walletAddress: string;
public readonly prevBlock: string;
public readonly startingNonce: number;
constructor(amountOfBlocks: number, walletAddress: string, prevBlock: string = '', startingNonce: number = 0) {
super();
this.amountOfBlocks = amountOfBlocks;
this.walletAddress = walletAddress;
this.prevBlock = prevBlock;
this.startingNonce = startingNonce;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
const params: { [key: string]: any } = {
'amount_of_blocks': this.amountOfBlocks,
'wallet_address': this.walletAddress,
'starting_nonce': this.startingNonce
}
if (this.prevBlock != '') {
params['prev_block'] = this.prevBlock;
}
dict['params'] = params;
return dict;
}
}

View file

@ -0,0 +1,6 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetAlternateChainsRequest extends JsonRPCRequest {
public override readonly method: string = 'get_alternate_chains';
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBansRequest extends JsonRPCRequest {
public override readonly method: string = 'get_bans';
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBlockCountRequest extends JsonRPCRequest {
public override readonly method: string = "get_block_count";
}

View file

@ -0,0 +1,19 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBlockHashRequest extends JsonRPCRequest {
public override readonly method: string = "on_get_block_hash";
public readonly blockHeight: number;
constructor(blockHeight: number) {
super();
this.blockHeight = blockHeight;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = [this.blockHeight];
return dict;
}
}

View file

@ -0,0 +1,25 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBlockHeaderByHashRequest extends JsonRPCRequest {
public override readonly method: string = 'get_block_header_by_hash';
public readonly hash: string;
public readonly fillPowHash: boolean;
constructor(hash: string, fillPowHash: boolean = false) {
super();
this.hash = hash;
this.fillPowHash = fillPowHash;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'hash': this.hash,
'fill_pow_hash': this.fillPowHash
}
return dict;
}
}

View file

@ -0,0 +1,24 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBlockHeaderByHeightRequest extends JsonRPCRequest {
public override readonly method: string = 'get_block_header_by_height';
public readonly height: number;
public readonly fillPowHash: boolean;
constructor(height: number, fillPowHash: boolean = false) {
super();
this.height = height;
this.fillPowHash = fillPowHash;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'height': this.height,
'fill_pow_hash': this.fillPowHash
}
return dict;
}
}

View file

@ -0,0 +1,27 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBlockHeadersRangeRequest extends JsonRPCRequest {
public override readonly method: string = 'get_block_headers_range';
public readonly startHeight: number;
public readonly endHeight: number;
public readonly fillPowHash: boolean;
constructor(startHeight: number, endHeight: number, fillPowHash: boolean = false) {
super();
this.startHeight = startHeight;
this.endHeight = endHeight;
this.fillPowHash = fillPowHash;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'start_height': this.startHeight,
'end_height': this.endHeight,
'fill_pow_hash': this.fillPowHash
}
return dict;
}
}

View file

@ -0,0 +1,24 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetBlockTemplateRequest extends JsonRPCRequest {
public override readonly method: string = "get_block_template";
public readonly walletAddress: string;
public readonly reserveSize: number;
constructor(walletAddress: string, reserveSize: number) {
super();
this.walletAddress = walletAddress;
this.reserveSize = reserveSize;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'wallet_address': this.walletAddress,
'reserve_size': this.reserveSize
}
return dict;
}
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetConnectionsRequest extends JsonRPCRequest {
public override readonly method: string = 'get_connections';
}

View file

@ -0,0 +1,23 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetFeeEstimateRequest extends JsonRPCRequest {
public override readonly method: string = 'get_fee_estimate';
public readonly graceBlocks: number;
constructor(graceBlocks: number = 0) {
super();
this.graceBlocks = graceBlocks;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
if (this.graceBlocks > 0) {
dict['params'] = {
'grace_blocks': this.graceBlocks
};
}
return dict;
}
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetInfoRequest extends JsonRPCRequest {
public override readonly method: string = 'get_info';
}

View file

@ -0,0 +1,21 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetLastBlockHeaderRequest extends JsonRPCRequest {
public override readonly method: string = 'get_last_block_header';
public readonly fillPowHash: boolean;
constructor(fillPowHash: boolean = false) {
super();
this.fillPowHash = fillPowHash;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'fill_pow_hash': this.fillPowHash
}
return dict;
}
}

View file

@ -0,0 +1,7 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetMinerDataRequest extends JsonRPCRequest {
public override readonly method: string = 'get_miner_data';
}

View file

@ -0,0 +1,33 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetOutputHistogramRequest extends JsonRPCRequest {
public override readonly method: string = 'get_output_histogram';
public readonly amounts: number[];
public readonly minCount: number;
public readonly maxCount: number;
public readonly unlocked: boolean;
public readonly recentCutoff: number;
constructor(amounts: number[], minCount: number, maxCount: number, unlocked: boolean, recentCutoff: number) {
super();
this.amounts = amounts;
this.minCount = minCount;
this.maxCount = maxCount;
this.unlocked = unlocked;
this.recentCutoff = recentCutoff;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'amounts': this.amounts,
'min_count': this.minCount,
'max_count': this.maxCount,
'unlocked': this.unlocked,
'recentCutoff': this.recentCutoff
}
return dict;
}
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class GetTxPoolBacklogRequest extends JsonRPCRequest {
public override readonly method: string = 'get_txpool_backlog';
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest"
export class GetVersionRequest extends JsonRPCRequest {
public override readonly method: string = 'get_version';
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class HardForkInfoRequest extends JsonRPCRequest {
public override readonly method: string = 'hard_fork_info';
}

View file

@ -0,0 +1,13 @@
export abstract class JsonRPCRequest {
public readonly version: string = "2.0";
public readonly id: string = "0";
public abstract readonly method: string;
public toDictionary(): { [key: string]: any } {
return {
"jsonrpc": this.version,
"id": this.id,
"method": this.method
}
}
}

View file

@ -0,0 +1,22 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class PruneBlockchainRequest extends JsonRPCRequest {
public override readonly method: string = 'prune_blockchain';
public readonly check: boolean;
constructor(check: boolean = false) {
super();
this.check = check;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'check': this.check
};
return dict;
}
}

View file

@ -0,0 +1,21 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class RelayTxRequest extends JsonRPCRequest {
public override readonly method: string = 'relay_tx';
public readonly txIds: string[];
constructor(txIds: string[]) {
super();
this.txIds = txIds;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
dict['params'] = {
'txids': this.txIds
}
return dict;
}
}

View file

@ -0,0 +1,26 @@
import { Ban } from "../Ban";
import { JsonRPCRequest } from "./JsonRPCRequest";
export class SetBansRequest extends JsonRPCRequest {
public override readonly method: string = 'set_bans';
public bans: Ban[];
constructor(bans: Ban[]) {
super();
this.bans = bans;
}
public override toDictionary(): { [key: string]: any; } {
const dict = super.toDictionary();
const bans: { [key: string]: any }[] = []
this.bans.forEach((ban) => bans.push(ban.toDictionary()));
dict['params'] = {
'bans': bans
}
return dict;
}
}

View file

@ -0,0 +1,19 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class SubmitBlockRequest extends JsonRPCRequest {
public override method: string = "submit_block";
public readonly blockBlobData: string[];
constructor(blockBlobData: string[]) {
super();
this.blockBlobData = blockBlobData;
}
public override toDictionary(): { [key: string]: any; } {
let dict = super.toDictionary();
dict['params'] = this.blockBlobData;
return dict;
}
}

View file

@ -0,0 +1,5 @@
import { JsonRPCRequest } from "./JsonRPCRequest";
export class SyncInfoRequest extends JsonRPCRequest {
public override readonly method: string = 'sync_info';
}

View file

@ -1,50 +1,81 @@
/* You can add global styles to this file, and also import other style files */
/*
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
*/
$primary: #ff5733;
@import "/node_modules/bootstrap/scss/bootstrap";
html, body {
margin: 0;
padding: 0;
background-color: #373636;
height: 100%;
font-family: Arial, Helvetica, sans-serif;
}
/* CAN (MUST) BE REMOVED ! Sample Global style */
.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: url(./assets/background.jpg) no-repeat center fixed;
-webkit-background-size: cover; /* pour anciens Chrome et Safari */
background-size: cover; /* version standardisée */
.title {
color: white;
margin: 0;
padding: 50px 20px;
}
a {
color: #fff !important;
text-transform: uppercase;
text-decoration: none;
background: #ed3330;
padding: 20px;
border-radius: 5px;
display: inline-block;
border: none;
transition: all 0.4s ease 0s;
&:hover {
background: #fff;
color: #ed3330 !important;
letter-spacing: 1px;
-webkit-box-shadow: 0px 5px 40px -10px rgba(0,0,0,0.57);
-moz-box-shadow: 0px 5px 40px -10px rgba(0,0,0,0.57);
box-shadow: 5px 40px -10px rgba(0,0,0,0.57);
transition: all 0.4s ease 0s;
}
}
html {
height: -webkit-fill-available;
}
main {
height: 100vh;
height: -webkit-fill-available;
max-height: 100vh;
overflow-x: auto;
overflow-y: hidden;
}
.dropdown-toggle { outline: 0; }
.btn-toggle {
padding: .25rem .5rem;
font-weight: 600;
color: var(--bs-emphasis-color);
background-color: transparent;
}
.btn-toggle:hover,
.btn-toggle:focus {
color: rgba(var(--bs-emphasis-color-rgb), .85);
background-color: var(--bs-tertiary-bg);
}
.btn-toggle::before {
width: 1.25em;
line-height: 0;
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%280,0,0,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
transition: transform .35s ease;
transform-origin: .5em 50%;
}
[data-bs-theme="dark"] .btn-toggle::before {
content: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='rgba%28255,255,255,.5%29' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 14l6-6-6-6'/%3e%3c/svg%3e");
}
.btn-toggle[aria-expanded="true"] {
color: rgba(var(--bs-emphasis-color-rgb), .85);
}
.btn-toggle[aria-expanded="true"]::before {
transform: rotate(90deg);
}
.btn-toggle-nav a {
padding: .1875rem .5rem;
margin-top: .125rem;
margin-left: 1.25rem;
}
.btn-toggle-nav a:hover,
.btn-toggle-nav a:focus {
background-color: var(--bs-tertiary-bg);
}
.scrollarea {
overflow-y: auto;
}
.nav-link.active {
background-color: #ff5733; /* Nuovo colore di sfondo */
border-color: #ff5733; /* Nuovo colore del bordo */
}