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 @@ + + + + + + + + +
HostIpSeconds
\ 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.header}}
+
+
{{card.content}}
+
+
+ } + +
+
+
+
+ + + + + + + + + + + +
Remote HostPeer IDHeightPrune SeedStateDownload
+
+
+
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.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.header}}
+
+
{{card.content}}
+
+
+ } + +
+
+
+
+ + + + + + + + + + +
Block HashHeightLengthMain Chain Parent BlockWide 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