diff --git a/.nx/cache/18.3.4-nx.linux-x64-gnu.node b/.nx/cache/18.3.4-nx.linux-x64-gnu.node
new file mode 100644
index 0000000..29fb952
Binary files /dev/null and b/.nx/cache/18.3.4-nx.linux-x64-gnu.node differ
diff --git a/angular.json b/angular.json
index a750f75..c598fe9 100644
--- a/angular.json
+++ b/angular.json
@@ -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": {
diff --git a/package-lock.json b/package-lock.json
index 13b0a58..fd36fa6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index 7dcb771..d8bb83e 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index ab5df28..fae3cea 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -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]
})
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 0680b43..e330bae 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1 +1,7 @@
-
+
+
diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index 89a6d1f..fbce227 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -1,3 +1,7 @@
:host {
+}
+
+body {
+ background-color: #373636;
}
\ No newline at end of file
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 78fbeb2..6ef3515 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -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]
})
diff --git a/src/app/bans/bans-routing.module.ts b/src/app/bans/bans-routing.module.ts
new file mode 100644
index 0000000..d89f8ee
--- /dev/null
+++ b/src/app/bans/bans-routing.module.ts
@@ -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 { }
diff --git a/src/app/bans/bans.component.html b/src/app/bans/bans.component.html
new file mode 100644
index 0000000..03cef98
--- /dev/null
+++ b/src/app/bans/bans.component.html
@@ -0,0 +1,14 @@
+
+
+
+ Host |
+ Ip |
+ Seconds |
+
+
+
\ No newline at end of file
diff --git a/src/app/bans/bans.component.scss b/src/app/bans/bans.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/bans/bans.component.spec.ts b/src/app/bans/bans.component.spec.ts
new file mode 100644
index 0000000..464d7fa
--- /dev/null
+++ b/src/app/bans/bans.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BansComponent } from './bans.component';
+
+describe('BansComponent', () => {
+ let component: BansComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [BansComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(BansComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/bans/bans.component.ts b/src/app/bans/bans.component.ts
new file mode 100644
index 0000000..73199e0
--- /dev/null
+++ b/src/app/bans/bans.component.ts
@@ -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 {
+ 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);
+
+ }
+
+}
diff --git a/src/app/bans/bans.module.ts b/src/app/bans/bans.module.ts
new file mode 100644
index 0000000..d42971f
--- /dev/null
+++ b/src/app/bans/bans.module.ts
@@ -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 { }
diff --git a/src/app/core/services/daemon/daemon.service.spec.ts b/src/app/core/services/daemon/daemon.service.spec.ts
new file mode 100644
index 0000000..2ccef39
--- /dev/null
+++ b/src/app/core/services/daemon/daemon.service.spec.ts
@@ -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();
+ });
+});
diff --git a/src/app/core/services/daemon/daemon.service.ts b/src/app/core/services/daemon/daemon.service.ts
new file mode 100644
index 0000000..49a73b4
--- /dev/null
+++ b/src/app/core/services/daemon/daemon.service.ts
@@ -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 {
+ const response = await this.callJsonRpc(new GetBlockCountRequest());
+
+ return BlockCount.parse(response.result);
+ }
+
+ public async getBlockHash(blockHeight: number): Promise {
+ 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 {
+ 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 {
+ const response = await this.callJsonRpc(new GenerateBlocksRequest(amountOfBlocks, walletAddress, prevBlock, startingNonce));
+
+ return GeneratedBlocks.parse(response.result);
+ }
+
+ public async getLastBlockHeader(fillPowHash: boolean = false): Promise {
+ const response = await this.callJsonRpc(new GetLastBlockHeaderRequest(fillPowHash));
+
+ return BlockHeader.parse(response.block_header);
+ }
+
+ public async getBlockHeaderByHash(hash: string, fillPowHash: boolean = false): Promise {
+ const response = await this.callJsonRpc(new GetBlockHeaderByHashRequest(hash, fillPowHash));
+
+ return BlockHeader.parse(response.block_header);
+ }
+
+ public async getBlockHeaderByHeight(height: number, fillPowHash: boolean = false): Promise {
+ 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 {
+ 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 {
+ 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 {
+ const response = await this.callJsonRpc(new GetInfoRequest());
+
+ return DaemonInfo.parse(response.result);
+ }
+
+ public async hardForkInfo(): Promise {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ const response = await this.callJsonRpc(new SyncInfoRequest());
+
+ return SyncInfo.parse(response.result);
+ }
+
+ public async getVersion(): Promise {
+ const response = await this.callJsonRpc(new GetVersionRequest());
+
+ return DaemonVersion.parse(response.result);
+ }
+
+ public async getFeeEstimate(): Promise {
+ const response = await this.callJsonRpc(new GetFeeEstimateRequest());
+
+ return FeeEstimate.parse(response.result);
+ }
+
+ public async getAlternateChains(): Promise {
+ 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 {
+ 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 {
+ const response = await this.callJsonRpc(new GetTxPoolBacklogRequest());
+
+ return TxBacklogEntry.fromBinary(response.backlog);
+ }
+
+ public async pruneBlockchain(check: boolean = false): Promise {
+ 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 {
+ const response = await this.callJsonRpc(new CalculatePoWHashRequest(majorVersion, height, blockBlob, seedHash));
+
+ return response.result;
+ }
+
+ public async flushCache(badTxs: boolean = false, badBlocks: boolean = false): Promise {
+ 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 {
+ const response = await this.callJsonRpc(new GetMinerDataRequest());
+
+ return MinerData.parse(response.result);
+ }
+
+}
+
diff --git a/src/app/detail/detail.component.html b/src/app/detail/detail.component.html
index 1d5b906..4d9c1ce 100644
--- a/src/app/detail/detail.component.html
+++ b/src/app/detail/detail.component.html
@@ -1,7 +1,39 @@
-
-
- {{ 'PAGES.DETAIL.TITLE' | translate }}
-
+
+
+
+ @for(card of cards; track card.header) {
+
+
+
+
{{card.content}}
+
+
+ }
+
+
+
+
+
+
+
+
+ Remote Host |
+ Peer ID |
+ Height |
+ Prune Seed |
+ State |
+ Download |
+
+
+
+
+
+
Spans
+
...
+
\ No newline at end of file
diff --git a/src/app/detail/detail.component.ts b/src/app/detail/detail.component.ts
index c089fa1..3e43222 100644
--- a/src/app/detail/detail.component.ts
+++ b/src/app/detail/detail.component.ts
@@ -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 {
+ 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 {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/app/hard-fork-info/hard-fork-info-routing.module.ts b/src/app/hard-fork-info/hard-fork-info-routing.module.ts
new file mode 100644
index 0000000..1ca873a
--- /dev/null
+++ b/src/app/hard-fork-info/hard-fork-info-routing.module.ts
@@ -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 { }
diff --git a/src/app/hard-fork-info/hard-fork-info.component.html b/src/app/hard-fork-info/hard-fork-info.component.html
new file mode 100644
index 0000000..74bdaec
--- /dev/null
+++ b/src/app/hard-fork-info/hard-fork-info.component.html
@@ -0,0 +1,10 @@
+
+ @for(card of cards; track card.header) {
+
+
+
+
{{card.content}}
+
+
+ }
+
\ No newline at end of file
diff --git a/src/app/hard-fork-info/hard-fork-info.component.scss b/src/app/hard-fork-info/hard-fork-info.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/hard-fork-info/hard-fork-info.component.spec.ts b/src/app/hard-fork-info/hard-fork-info.component.spec.ts
new file mode 100644
index 0000000..6d31de9
--- /dev/null
+++ b/src/app/hard-fork-info/hard-fork-info.component.spec.ts
@@ -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;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HardForkInfoComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(HardForkInfoComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/hard-fork-info/hard-fork-info.component.ts b/src/app/hard-fork-info/hard-fork-info.component.ts
new file mode 100644
index 0000000..d065d32
--- /dev/null
+++ b/src/app/hard-fork-info/hard-fork-info.component.ts
@@ -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 {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/app/hard-fork-info/hard-fork-info.module.ts b/src/app/hard-fork-info/hard-fork-info.module.ts
new file mode 100644
index 0000000..4a702d4
--- /dev/null
+++ b/src/app/hard-fork-info/hard-fork-info.module.ts
@@ -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 { }
diff --git a/src/app/mining/mining-routing.module.ts b/src/app/mining/mining-routing.module.ts
new file mode 100644
index 0000000..2d6f6ba
--- /dev/null
+++ b/src/app/mining/mining-routing.module.ts
@@ -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 { }
diff --git a/src/app/mining/mining.component.html b/src/app/mining/mining.component.html
new file mode 100644
index 0000000..6f7652f
--- /dev/null
+++ b/src/app/mining/mining.component.html
@@ -0,0 +1,53 @@
+
+
+
+
+ @for(card of cards; track card.header) {
+
+
+
+
{{card.content}}
+
+
+ }
+
+
+
+
+
+
+
+
+ Block Hash |
+ Height |
+ Length |
+ Main Chain Parent Block |
+ Wide Difficulty |
+
+
+
+
+
+
+
+
+ Address
+
+
+
+
+ Reserve Size
+
+
+
+
+
+
+
+
...
+
\ No newline at end of file
diff --git a/src/app/mining/mining.component.scss b/src/app/mining/mining.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/mining/mining.component.spec.ts b/src/app/mining/mining.component.spec.ts
new file mode 100644
index 0000000..8e29b9c
--- /dev/null
+++ b/src/app/mining/mining.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MiningComponent } from './mining.component';
+
+describe('MiningComponent', () => {
+ let component: MiningComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [MiningComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(MiningComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/mining/mining.component.ts b/src/app/mining/mining.component.ts
new file mode 100644
index 0000000..eff6c44
--- /dev/null
+++ b/src/app/mining/mining.component.ts
@@ -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 {
+ 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;
+ }
+}
\ No newline at end of file
diff --git a/src/app/mining/mining.module.ts b/src/app/mining/mining.module.ts
new file mode 100644
index 0000000..8496248
--- /dev/null
+++ b/src/app/mining/mining.module.ts
@@ -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 { }
diff --git a/src/app/navbar/navbar.component.html b/src/app/navbar/navbar.component.html
new file mode 100644
index 0000000..e81f0fc
--- /dev/null
+++ b/src/app/navbar/navbar.component.html
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/src/app/navbar/navbar.component.scss b/src/app/navbar/navbar.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/navbar/navbar.component.spec.ts b/src/app/navbar/navbar.component.spec.ts
new file mode 100644
index 0000000..78867a6
--- /dev/null
+++ b/src/app/navbar/navbar.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { NavbarComponent } from './navbar.component';
+
+describe('NavbarComponent', () => {
+ let component: NavbarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [NavbarComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(NavbarComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts
new file mode 100644
index 0000000..cb373db
--- /dev/null
+++ b/src/app/navbar/navbar.component.ts
@@ -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) {
+
+ }
+}
diff --git a/src/app/navbar/navbar.model.ts b/src/app/navbar/navbar.model.ts
new file mode 100644
index 0000000..86ce0d2
--- /dev/null
+++ b/src/app/navbar/navbar.model.ts
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/app/navbar/navbar.service.spec.ts b/src/app/navbar/navbar.service.spec.ts
new file mode 100644
index 0000000..f9c15ab
--- /dev/null
+++ b/src/app/navbar/navbar.service.spec.ts
@@ -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();
+ });
+});
diff --git a/src/app/navbar/navbar.service.ts b/src/app/navbar/navbar.service.ts
new file mode 100644
index 0000000..b8b9059
--- /dev/null
+++ b/src/app/navbar/navbar.service.ts
@@ -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([]);
+ }
+
+}
diff --git a/src/app/sidebar/sidebar.component.html b/src/app/sidebar/sidebar.component.html
new file mode 100644
index 0000000..7294bbc
--- /dev/null
+++ b/src/app/sidebar/sidebar.component.html
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/app/sidebar/sidebar.component.scss b/src/app/sidebar/sidebar.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/sidebar/sidebar.component.spec.ts b/src/app/sidebar/sidebar.component.spec.ts
new file mode 100644
index 0000000..85e49bd
--- /dev/null
+++ b/src/app/sidebar/sidebar.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SidebarComponent } from './sidebar.component';
+
+describe('SidebarComponent', () => {
+ let component: SidebarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [SidebarComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(SidebarComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/sidebar/sidebar.component.ts b/src/app/sidebar/sidebar.component.ts
new file mode 100644
index 0000000..0feecd1
--- /dev/null
+++ b/src/app/sidebar/sidebar.component.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 95c3bbc..ad6c44c 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -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"
}
}
diff --git a/src/assets/icons/monero-symbol-on-white-480.png b/src/assets/icons/monero-symbol-on-white-480.png
new file mode 100644
index 0000000..58c93c4
Binary files /dev/null and b/src/assets/icons/monero-symbol-on-white-480.png differ
diff --git a/src/common/Ban.ts b/src/common/Ban.ts
new file mode 100644
index 0000000..67088fd
--- /dev/null
+++ b/src/common/Ban.ts
@@ -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);
+ }
+}
diff --git a/src/common/BlockCount.ts b/src/common/BlockCount.ts
new file mode 100644
index 0000000..fab75f5
--- /dev/null
+++ b/src/common/BlockCount.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/BlockHeader.ts b/src/common/BlockHeader.ts
new file mode 100644
index 0000000..f94aab1
--- /dev/null
+++ b/src/common/BlockHeader.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/BlockTemplate.ts b/src/common/BlockTemplate.ts
new file mode 100644
index 0000000..63e2f85
--- /dev/null
+++ b/src/common/BlockTemplate.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/BlockchainPruneInfo.ts b/src/common/BlockchainPruneInfo.ts
new file mode 100644
index 0000000..f12bc74
--- /dev/null
+++ b/src/common/BlockchainPruneInfo.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/Chain.ts b/src/common/Chain.ts
new file mode 100644
index 0000000..4b92f28
--- /dev/null
+++ b/src/common/Chain.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/Connection.ts b/src/common/Connection.ts
new file mode 100644
index 0000000..b0e11b0
--- /dev/null
+++ b/src/common/Connection.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/DaemonInfo.ts b/src/common/DaemonInfo.ts
new file mode 100644
index 0000000..5bd1827
--- /dev/null
+++ b/src/common/DaemonInfo.ts
@@ -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
+ );
+ }
+}
diff --git a/src/common/DaemonVersion.ts b/src/common/DaemonVersion.ts
new file mode 100644
index 0000000..54aabf6
--- /dev/null
+++ b/src/common/DaemonVersion.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/FeeEstimate.ts b/src/common/FeeEstimate.ts
new file mode 100644
index 0000000..63a942a
--- /dev/null
+++ b/src/common/FeeEstimate.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/GeneratedBlocks.ts b/src/common/GeneratedBlocks.ts
new file mode 100644
index 0000000..394701c
--- /dev/null
+++ b/src/common/GeneratedBlocks.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/HardForkInfo.ts b/src/common/HardForkInfo.ts
new file mode 100644
index 0000000..00bb0fa
--- /dev/null
+++ b/src/common/HardForkInfo.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/HistogramEntry.ts b/src/common/HistogramEntry.ts
new file mode 100644
index 0000000..cd29b76
--- /dev/null
+++ b/src/common/HistogramEntry.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/MineableTxBacklog.ts b/src/common/MineableTxBacklog.ts
new file mode 100644
index 0000000..2d1f57b
--- /dev/null
+++ b/src/common/MineableTxBacklog.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/MinerData.ts b/src/common/MinerData.ts
new file mode 100644
index 0000000..f7393a4
--- /dev/null
+++ b/src/common/MinerData.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/Peer.ts b/src/common/Peer.ts
new file mode 100644
index 0000000..87ef431
--- /dev/null
+++ b/src/common/Peer.ts
@@ -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));
+ }
+}
\ No newline at end of file
diff --git a/src/common/Span.ts b/src/common/Span.ts
new file mode 100644
index 0000000..8cec858
--- /dev/null
+++ b/src/common/Span.ts
@@ -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);
+ }
+}
\ No newline at end of file
diff --git a/src/common/SyncInfo.ts b/src/common/SyncInfo.ts
new file mode 100644
index 0000000..b5e960c
--- /dev/null
+++ b/src/common/SyncInfo.ts
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/common/TxBacklogEntry.ts b/src/common/TxBacklogEntry.ts
new file mode 100644
index 0000000..597201b
--- /dev/null
+++ b/src/common/TxBacklogEntry.ts
@@ -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");
+ }
+}
\ No newline at end of file
diff --git a/src/common/request.ts b/src/common/request.ts
new file mode 100644
index 0000000..6a0036f
--- /dev/null
+++ b/src/common/request.ts
@@ -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";
\ No newline at end of file
diff --git a/src/common/request/BannedRequest.ts b/src/common/request/BannedRequest.ts
new file mode 100644
index 0000000..d6b6731
--- /dev/null
+++ b/src/common/request/BannedRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/CalculatePoWHashRequest.ts b/src/common/request/CalculatePoWHashRequest.ts
new file mode 100644
index 0000000..5268bc8
--- /dev/null
+++ b/src/common/request/CalculatePoWHashRequest.ts
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/common/request/FlushCacheRequest.ts b/src/common/request/FlushCacheRequest.ts
new file mode 100644
index 0000000..c3300aa
--- /dev/null
+++ b/src/common/request/FlushCacheRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/FlushTxPoolRequest.ts b/src/common/request/FlushTxPoolRequest.ts
new file mode 100644
index 0000000..4e9c22e
--- /dev/null
+++ b/src/common/request/FlushTxPoolRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GenerateBlocksRequest.ts b/src/common/request/GenerateBlocksRequest.ts
new file mode 100644
index 0000000..9c492ac
--- /dev/null
+++ b/src/common/request/GenerateBlocksRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetAlternateChainsRequest.ts b/src/common/request/GetAlternateChainsRequest.ts
new file mode 100644
index 0000000..ce5f0a1
--- /dev/null
+++ b/src/common/request/GetAlternateChainsRequest.ts
@@ -0,0 +1,6 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetAlternateChainsRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_alternate_chains';
+
+}
\ No newline at end of file
diff --git a/src/common/request/GetBansRequest.ts b/src/common/request/GetBansRequest.ts
new file mode 100644
index 0000000..450c221
--- /dev/null
+++ b/src/common/request/GetBansRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetBansRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_bans';
+}
\ No newline at end of file
diff --git a/src/common/request/GetBlockCountRequest.ts b/src/common/request/GetBlockCountRequest.ts
new file mode 100644
index 0000000..c0a2eae
--- /dev/null
+++ b/src/common/request/GetBlockCountRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetBlockCountRequest extends JsonRPCRequest {
+ public override readonly method: string = "get_block_count";
+}
diff --git a/src/common/request/GetBlockHashRequest.ts b/src/common/request/GetBlockHashRequest.ts
new file mode 100644
index 0000000..b49eabc
--- /dev/null
+++ b/src/common/request/GetBlockHashRequest.ts
@@ -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;
+ }
+ }
\ No newline at end of file
diff --git a/src/common/request/GetBlockHeaderByHashRequest.ts b/src/common/request/GetBlockHeaderByHashRequest.ts
new file mode 100644
index 0000000..c97591c
--- /dev/null
+++ b/src/common/request/GetBlockHeaderByHashRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetBlockHeaderByHeightRequest.ts b/src/common/request/GetBlockHeaderByHeightRequest.ts
new file mode 100644
index 0000000..cfe7154
--- /dev/null
+++ b/src/common/request/GetBlockHeaderByHeightRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetBlockHeadersRangeRequest.ts b/src/common/request/GetBlockHeadersRangeRequest.ts
new file mode 100644
index 0000000..23c9dc4
--- /dev/null
+++ b/src/common/request/GetBlockHeadersRangeRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetBlockTemplateRequest.ts b/src/common/request/GetBlockTemplateRequest.ts
new file mode 100644
index 0000000..a8d5c12
--- /dev/null
+++ b/src/common/request/GetBlockTemplateRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetConnectionsRequest.ts b/src/common/request/GetConnectionsRequest.ts
new file mode 100644
index 0000000..4287779
--- /dev/null
+++ b/src/common/request/GetConnectionsRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetConnectionsRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_connections';
+}
\ No newline at end of file
diff --git a/src/common/request/GetFeeEstimateRequest.ts b/src/common/request/GetFeeEstimateRequest.ts
new file mode 100644
index 0000000..32bcd43
--- /dev/null
+++ b/src/common/request/GetFeeEstimateRequest.ts
@@ -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;
+ }
+
+}
\ No newline at end of file
diff --git a/src/common/request/GetInfoRequest.ts b/src/common/request/GetInfoRequest.ts
new file mode 100644
index 0000000..14f28ab
--- /dev/null
+++ b/src/common/request/GetInfoRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetInfoRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_info';
+}
\ No newline at end of file
diff --git a/src/common/request/GetLastBlockHeaderRequest.ts b/src/common/request/GetLastBlockHeaderRequest.ts
new file mode 100644
index 0000000..0e8c2c1
--- /dev/null
+++ b/src/common/request/GetLastBlockHeaderRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetMinerDataRequest.ts b/src/common/request/GetMinerDataRequest.ts
new file mode 100644
index 0000000..8e707d8
--- /dev/null
+++ b/src/common/request/GetMinerDataRequest.ts
@@ -0,0 +1,7 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetMinerDataRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_miner_data';
+
+
+}
\ No newline at end of file
diff --git a/src/common/request/GetOutputHistogramRequest.ts b/src/common/request/GetOutputHistogramRequest.ts
new file mode 100644
index 0000000..d161c02
--- /dev/null
+++ b/src/common/request/GetOutputHistogramRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/GetTxPoolBacklogRequest.ts b/src/common/request/GetTxPoolBacklogRequest.ts
new file mode 100644
index 0000000..fff69ea
--- /dev/null
+++ b/src/common/request/GetTxPoolBacklogRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class GetTxPoolBacklogRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_txpool_backlog';
+}
diff --git a/src/common/request/GetVersionRequest.ts b/src/common/request/GetVersionRequest.ts
new file mode 100644
index 0000000..5e17332
--- /dev/null
+++ b/src/common/request/GetVersionRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest"
+
+export class GetVersionRequest extends JsonRPCRequest {
+ public override readonly method: string = 'get_version';
+}
\ No newline at end of file
diff --git a/src/common/request/HardForkInfoRequest.ts b/src/common/request/HardForkInfoRequest.ts
new file mode 100644
index 0000000..69e8a31
--- /dev/null
+++ b/src/common/request/HardForkInfoRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class HardForkInfoRequest extends JsonRPCRequest {
+ public override readonly method: string = 'hard_fork_info';
+}
diff --git a/src/common/request/JsonRPCRequest.ts b/src/common/request/JsonRPCRequest.ts
new file mode 100644
index 0000000..67597f8
--- /dev/null
+++ b/src/common/request/JsonRPCRequest.ts
@@ -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
+ }
+ }
+ }
\ No newline at end of file
diff --git a/src/common/request/PruneBlockchainRequest.ts b/src/common/request/PruneBlockchainRequest.ts
new file mode 100644
index 0000000..6bcec8d
--- /dev/null
+++ b/src/common/request/PruneBlockchainRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/RelayTxRequest.ts b/src/common/request/RelayTxRequest.ts
new file mode 100644
index 0000000..5c1af71
--- /dev/null
+++ b/src/common/request/RelayTxRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/SetBansRequest.ts b/src/common/request/SetBansRequest.ts
new file mode 100644
index 0000000..37b24a6
--- /dev/null
+++ b/src/common/request/SetBansRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/SubmitBlockRequest.ts b/src/common/request/SubmitBlockRequest.ts
new file mode 100644
index 0000000..da51a83
--- /dev/null
+++ b/src/common/request/SubmitBlockRequest.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/common/request/SyncInfoRequest.ts b/src/common/request/SyncInfoRequest.ts
new file mode 100644
index 0000000..6911906
--- /dev/null
+++ b/src/common/request/SyncInfoRequest.ts
@@ -0,0 +1,5 @@
+import { JsonRPCRequest } from "./JsonRPCRequest";
+
+export class SyncInfoRequest extends JsonRPCRequest {
+ public override readonly method: string = 'sync_info';
+}
\ No newline at end of file
diff --git a/src/styles.scss b/src/styles.scss
index 4c1c4b2..5d0ad43 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -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 */
+}
\ No newline at end of file