From 5deb58f1359c64dc9863fece6d370db1454e26ef Mon Sep 17 00:00:00 2001 From: everoddandeven Date: Wed, 25 Sep 2024 18:14:08 +0200 Subject: [PATCH] Save settings to IndexDB and other fixes --- package-lock.json | 6 ++ package.json | 1 + src/app/app.component.ts | 15 +++ .../core/services/daemon/daemon.service.ts | 66 ++++++++++++- src/app/pages/detail/detail.component.html | 34 +++++-- src/app/pages/detail/detail.component.ts | 93 ++++++++++++------- .../pages/settings/settings.component.html | 54 +++++------ src/app/pages/settings/settings.component.ts | 51 ++++++++-- src/app/pages/settings/settings.module.ts | 3 +- .../components/sidebar/sidebar.component.html | 6 +- .../components/sidebar/sidebar.component.ts | 33 ++++++- src/common/DaemonSettings.ts | 2 +- 12 files changed, 278 insertions(+), 86 deletions(-) diff --git a/package-lock.json b/package-lock.json index fd36fa6..a6c9cc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "bootstrap": "5.3.3", "bootstrap-icons": "1.11.3", "bootstrap-table": "1.23.2", + "idb": "8.0.0", "jquery": "3.7.1", "rxjs": "7.8.1", "tslib": "2.6.2", @@ -13593,6 +13594,11 @@ "postcss": "^8.1.0" } }, + "node_modules/idb": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", + "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", diff --git a/package.json b/package.json index d8bb83e..50772f8 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "bootstrap": "5.3.3", "bootstrap-icons": "1.11.3", "bootstrap-table": "1.23.2", + "idb": "8.0.0", "jquery": "3.7.1", "rxjs": "7.8.1", "tslib": "2.6.2", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index bdcb6dc..173d4f9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -38,6 +38,21 @@ export class AppComponent { private async load(): Promise { this.loading = true; + if (!window.indexedDB) { + console.log("Il tuo browser non supporta indexedDB"); + } + else { + console.log("Browser supports IndexedDB"); + var request = window.indexedDB.open("dati", 1); + console.log(request); + + request.onsuccess = function(event: Event) { + if(event.target instanceof IDBOpenDBRequest) { + console.log(event.target.result) + } + }; + } + try { this.daemonRunning = await this.daemonService.isRunning(); } diff --git a/src/app/core/services/daemon/daemon.service.ts b/src/app/core/services/daemon/daemon.service.ts index 16636a3..307ce08 100644 --- a/src/app/core/services/daemon/daemon.service.ts +++ b/src/app/core/services/daemon/daemon.service.ts @@ -1,5 +1,5 @@ import { HttpClient } from '@angular/common/http'; -import { Injectable } from '@angular/core'; +import { EventEmitter, Injectable } from '@angular/core'; import { BlockCount } from '../../../../common/BlockCount'; import { firstValueFrom } from 'rxjs'; import { @@ -72,11 +72,17 @@ import { MiningStatus } from '../../../../common/MiningStatus'; import { TxInfo } from '../../../../common/TxInfo'; import { DaemonSettings } from '../../../../common/DaemonSettings'; import { MethodNotFoundError } from '../../../../common/error/MethodNotFoundError'; +import { openDB, IDBPDatabase } from "idb" +import { resolve } from 'path'; @Injectable({ providedIn: 'root' }) export class DaemonService { + private dbName = 'DaemonSettingsDB'; + private storeName = 'settingsStore'; + private openDbPromise: Promise; + private daemonRunning?: boolean; private url: string = "http://127.0.0.1:28081"; public settings: DaemonSettings; @@ -84,16 +90,58 @@ export class DaemonService { //private url: string = "https://testnet.xmr.ditatompel.com"; //private url: string = "https://xmr.yemekyedim.com:18081"; //private url: string = "https://moneronode.org:18081"; - + + public readonly onDaemonStart: EventEmitter = new EventEmitter(); + private readonly headers: { [key: string]: string } = { "Access-Control-Allow-Headers": "*", // this will allow all CORS requests "Access-Control-Allow-Methods": 'POST,GET' // this states the allowed methods }; constructor(private httpClient: HttpClient, private electronService: ElectronService) { + this.openDbPromise = this.openDatabase(); this.settings = this.loadSettings(); } + private async openDatabase(): Promise { + return openDB(this.dbName, 1, { + upgrade(db) { + // Crea un archivio (store) per i settings se non esiste giĆ  + if (!db.objectStoreNames.contains('settingsStore')) { + db.createObjectStore('settingsStore', { + keyPath: 'id', + autoIncrement: true, + }); + } + }, + }); + } + + public async saveSettings(settings: DaemonSettings): Promise { + const db = await this.openDbPromise; + await db.put(this.storeName, { id: 1, ...settings }); + this.settings = settings; + } + + public async getSettings(): Promise { + const db = await this.openDbPromise; + const result = await db.get(this.storeName, 1); + if (result) { + this.settings = DaemonSettings.parse(result); + } + else + { + this.settings = new DaemonSettings(); + } + + return this.settings; + } + + public async deleteSettings(): Promise { + const db = await this.openDbPromise; + await db.delete(this.storeName, 1); + } + private loadSettings(): DaemonSettings { /* const args = [ @@ -164,10 +212,20 @@ export class DaemonService { } console.log("Starting daemon"); + const settings = await this.getSettings(); + this.electronService.ipcRenderer.send('start-monerod', settings.toCommandOptions()); - this.electronService.ipcRenderer.send('start-monerod', this.settings.toCommandOptions()); + await new Promise(f => setTimeout(f, 3000)); - console.log("Daemon started"); + if (await this.isRunning(true)) { + console.log("Daemon started"); + this.onDaemonStart.emit(true); + } + else + { + console.log("Daemon not started"); + this.onDaemonStart.emit(false); + } setTimeout(() => { }, 500) diff --git a/src/app/pages/detail/detail.component.html b/src/app/pages/detail/detail.component.html index 1d99170..04f6b9a 100644 --- a/src/app/pages/detail/detail.component.html +++ b/src/app/pages/detail/detail.component.html @@ -1,10 +1,8 @@ - - -
+
-
+

Daemon not running

Start monero daemon

@@ -15,12 +13,30 @@
@for(card of cards; track card.header) { -
-
{{card.header}}
-
-
{{card.content}}
+ @if(card.loading) { + -
+ } + @else { +
+
{{card.header}}
+
+
{{card.content}}
+
+
+ } + }
diff --git a/src/app/pages/detail/detail.component.ts b/src/app/pages/detail/detail.component.ts index 75af5db..c8546d0 100644 --- a/src/app/pages/detail/detail.component.ts +++ b/src/app/pages/detail/detail.component.ts @@ -7,6 +7,9 @@ import { NavbarService } from '../../shared/components/navbar/navbar.service'; import { NavigationEnd, Router } from '@angular/router'; import { DaemonInfo } from '../../../common/DaemonInfo'; +import * as $ from 'jquery'; +import * as bootstrapTable from 'bootstrap-table'; + @Component({ selector: 'app-detail', templateUrl: './detail.component.html', @@ -72,12 +75,12 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { new NavbarLink('pills-profile-tab', '#pills-profile', 'pills-profile', false, 'Peers', true) ]; - this.cards = []; + this.cards = this.createLoadingCards(); this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { if (event.url != '/detail') return; - this.onNavigationEnd(); + //this.onNavigationEnd(); } }); } @@ -89,21 +92,24 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { ngAfterViewInit(): void { console.log('DetailComponent AFTER VIEW INIT'); this.navbarService.setNavbarLinks(this.navbarLinks); + + this.load().then(() => { + this.cards = this.createCards(); + }); - setTimeout(() => { - this.ngZone.run(() => { - if (this.isLoading) { - return; - } - const $table = $('#table'); - $table.bootstrapTable({}); - $table.bootstrapTable('refreshOptions', { - classes: 'table table-bordered table-hover table-dark table-striped' - }); - this.load(); - + this.loadInterval = setInterval(() => { + /* + const $table = $('#table'); + $table.bootstrapTable({}); + $table.bootstrapTable('refreshOptions', { + classes: 'table table-bordered table-hover table-dark table-striped' }); - }, 500); + */ + + this.load().then(() => { + this.cards = this.createCards(); + }); + }, 5000); } ngOnDestroy(): void { @@ -121,27 +127,50 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { } this.startingDaemon = true; - try { - await this.daemonService.startDaemon(); - this.daemonRunning = await this.daemonService.isRunning(); - } - catch(error) { - console.error(error); - } - - this.startingDaemon = false; + setTimeout(async () => { + try { + await this.daemonService.startDaemon(); + this.daemonRunning = await this.daemonService.isRunning(); + } + catch(error) { + console.error(error); + this.daemonRunning = false; + } + + this.startingDaemon = false; + }, 500); } private onNavigationEnd(): void { this.load().then(() => { - this.cards = this.createCards(); + //this.cards = this.createCards(); }); } + private createLoadingCards(): Card[] { + return [ + new Card('Connection Status', this.connectionStatus, true), + new Card('Network Type', this.networkType, true), + new Card('Node Type', this.nodeType, true), + new Card('Sync progress', this.syncProgress, true), + new Card('Scan Height', `${this.height} / ${this.targetHeight}`, true), + new Card('Next needed pruning seed', `${this.nextNeededPruningSeed}`, true), + new Card('Block count', `${this.blockCount}`, true), + new Card('Monero version', this.version, true), + new Card('Blockchain size', this.blockchainSize, true), + new Card('Disk usage', this.diskUsage, true), + new Card('Transaction count', `${this.txCount}`, true), + new Card('Pool size', `${this.poolSize}`, true) + ]; + } + private createCards(): Card[] { if (!this.daemonRunning) { - return [] + return []; + } + if (this.isLoading) { + return this.createLoadingCards(); } return [ new Card('Connection Status', this.connectionStatus), @@ -193,9 +222,9 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { this.blockCount = blockCount.count; - const version = await this.daemonService.getVersion(); + //const version = await this.daemonService.getVersion(); - this.version = `${version.version}`; + //this.version = `${version.version}`; this.daemonInfo = await this.daemonService.getInfo(); @@ -211,8 +240,8 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { this.version = this.daemonInfo.version; this.syncProgress = `${(this.height*100/this.targetHeight).toFixed(2)} %`; - //const blockchainPruned = await this.isBlockchainPruned(); - const blockchainPruned = false; + const blockchainPruned = await this.isBlockchainPruned(); + //const blockchainPruned = false; this.nodeType = blockchainPruned ? 'pruned' : 'full'; $table.bootstrapTable('load', this.getPeers()); } @@ -251,9 +280,11 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy { class Card { public header: string; public content: string; + public loading: boolean; - constructor(header: string, content: string) { + constructor(header: string, content: string, loading: boolean = false) { this.header = header; this.content = content; + this.loading = loading; } } \ No newline at end of file diff --git a/src/app/pages/settings/settings.component.html b/src/app/pages/settings/settings.component.html index 2778397..43a5555 100644 --- a/src/app/pages/settings/settings.component.html +++ b/src/app/pages/settings/settings.component.html @@ -1,4 +1,4 @@ -
+
@@ -7,42 +7,42 @@
- +
Do not listen for peers, nor connect to any
- +
Allow other users to use the node as a remote (restricted RPC mode, view-only commands) and advertise it over P2P
- +
Restrict RPC to view-only commands and do not return privacy sensitive data in RPC calls
- +
Confirm Bind IP is not a loopback (local) IP
- +
Ignore unsuccessful IPv4 bind for RPC
- +
Do not ban hosts on RPC errors
@@ -85,7 +85,7 @@
- +
Allow IPv6 for RPC
@@ -161,7 +161,7 @@
- +
Allow free access from the loopback address (ie, the local host)
@@ -176,23 +176,23 @@
- +
Allow user chain certificates
- +
Allow any peer certificate
@@ -229,14 +229,14 @@
- +
Allow local ip add to peer list, mostly in debug process
- +
Ignore unsuccessful IPv4 bind for P2P
@@ -332,21 +332,21 @@
- +
Reduce blockchain disk usage
- +
Allow syncing from nodes with only pruned blocks
- +
Sync up most of the way by using embedded, known block hashes
@@ -360,7 +360,7 @@
- +
Keep alternative blocks on restart
@@ -379,6 +379,8 @@
+ +
@@ -386,15 +388,15 @@
- +
- +
- +
@@ -415,14 +417,14 @@
- +
Enable background mining
- +
Reduce blockchain disk usage
@@ -491,6 +493,6 @@
- +
diff --git a/src/app/pages/settings/settings.component.ts b/src/app/pages/settings/settings.component.ts index 645463a..8b1d30a 100644 --- a/src/app/pages/settings/settings.component.ts +++ b/src/app/pages/settings/settings.component.ts @@ -4,13 +4,12 @@ import { NavigationEnd, NavigationStart, Router } from '@angular/router'; import { NavbarLink } from '../../shared/components/navbar/navbar.model'; import { DaemonSettings } from '../../../common/DaemonSettings'; import { FormsModule, NgModel } from '@angular/forms'; +import { DaemonService } from '../../core/services/daemon/daemon.service'; @Component({ selector: 'app-settings', templateUrl: './settings.component.html', - styleUrl: './settings.component.scss', - imports: [FormsModule], - standalone: true + styleUrl: './settings.component.scss' }) export class SettingsComponent implements AfterViewInit { @@ -20,9 +19,12 @@ export class SettingsComponent implements AfterViewInit { public rpcLoginUser: string; public rpcLoginPassword: string; + public loading: boolean; - constructor(private router: Router, private navbarService: NavbarService) { - + public networkType: 'mainnet' | 'testnet' | 'stagenet' = 'mainnet'; + + constructor(private router: Router, private navbarService: NavbarService, private daemonService: DaemonService) { + this.loading = true; this.navbarLinks = [ new NavbarLink('pills-rpc-tab', '#pills-rpc', 'pills-rpc', true, 'RPC'), @@ -50,6 +52,17 @@ export class SettingsComponent implements AfterViewInit { this.onNavigationEnd(); } }); + + this.load(); + } + + private async load(): Promise { + console.log("getting settings"); + this.originalSettings = await this.daemonService.getSettings(); + this.currentSettings = this.originalSettings.clone(); + this.loading = false; + + this.networkType = this.currentSettings.mainnet ? 'mainnet' : this.currentSettings.testnet ? 'testnet' : this.currentSettings.stagenet ? 'stagenet' : 'mainnet'; } public get modified(): boolean { @@ -105,12 +118,36 @@ export class SettingsComponent implements AfterViewInit { this.currentSettings.noFluffyBlocks = !this.currentSettings.noFluffyBlocks; } + public OnNetworkTypeChange(): void { + if (this.networkType == 'mainnet') { + this.currentSettings.mainnet = true; + this.currentSettings.testnet = false; + this.currentSettings.stagenet = false; + } + else if (this.networkType == 'testnet') { + this.currentSettings.mainnet = false; + this.currentSettings.testnet = true; + this.currentSettings.stagenet = false; + } + else if (this.networkType == 'stagenet') { + this.currentSettings.mainnet = false; + this.currentSettings.testnet = false; + this.currentSettings.stagenet = true; + } + } + private onNavigationEnd(): void { } - public OnSave(): void { - + public async OnSave(): Promise { + if (!this.modified) { + return; + } + + await this.daemonService.saveSettings(this.currentSettings); + + this.originalSettings = this.currentSettings.clone(); } } /** diff --git a/src/app/pages/settings/settings.module.ts b/src/app/pages/settings/settings.module.ts index 754242d..f131c86 100644 --- a/src/app/pages/settings/settings.module.ts +++ b/src/app/pages/settings/settings.module.ts @@ -4,10 +4,11 @@ import { CommonModule } from '@angular/common'; import { SettingsRoutingModule } from './settings-routing.module'; import { SharedModule } from '../../shared/shared.module'; import { FormsModule } from '@angular/forms'; +import { SettingsComponent } from './settings.component'; @NgModule({ - declarations: [], + declarations: [SettingsComponent], imports: [ CommonModule, FormsModule, diff --git a/src/app/shared/components/sidebar/sidebar.component.html b/src/app/shared/components/sidebar/sidebar.component.html index 4a7bd5f..9c60513 100644 --- a/src/app/shared/components/sidebar/sidebar.component.html +++ b/src/app/shared/components/sidebar/sidebar.component.html @@ -1,10 +1,10 @@ -
+
diff --git a/src/app/shared/components/sidebar/sidebar.component.ts b/src/app/shared/components/sidebar/sidebar.component.ts index 9b28063..17047d8 100644 --- a/src/app/shared/components/sidebar/sidebar.component.ts +++ b/src/app/shared/components/sidebar/sidebar.component.ts @@ -1,6 +1,7 @@ import { CommonModule, NgClass, NgFor } from '@angular/common'; import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ChildActivationEnd, ChildActivationStart, NavigationCancel, NavigationEnd, NavigationError, NavigationStart, RouteConfigLoadEnd, RouteConfigLoadStart, Router, RouterEvent, RouterModule, RoutesRecognized } from '@angular/router'; +import { DaemonService } from '../../../core/services/daemon/daemon.service'; @Component({ selector: 'app-sidebar', @@ -10,11 +11,37 @@ import { ChildActivationEnd, ChildActivationStart, NavigationCancel, NavigationE export class SidebarComponent implements OnChanges { @Input() public isDaemonRunning: boolean = false; - public navLinks: NavLink[]; + public navLinks: NavLink[] = []; public isLoading: boolean; public errorMessage: string; - constructor(private router: Router) { + constructor(private router: Router, private daemonService: DaemonService) { + this.updateLinks(); + this.isLoading = false; + this.errorMessage = ''; + this.daemonService.onDaemonStart.subscribe((started: boolean) => { + if (!started) { + this.navLinks = [ + new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'), + new NavLink('Settings', '/settings', 'bi bi-gear') + ]; + } + else { + this.navLinks = [ + new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'), + new NavLink('Blockchain', '/blockchain', 'bi bi-bounding-box'), + new NavLink('Transactions', '/transactions', 'bi bi-credit-card-2-front'), + new NavLink('Outputs', '/outputs', 'bi bi-circle-fill'), + new NavLink('Mining', '/mining', 'bi bi-minecart-loaded'), + new NavLink('Hard Fork Info', '/hardforkinfo', 'bi bi-signpost-split'), + new NavLink('Bans', '/bans', 'bi bi-ban'), + new NavLink('Settings', '/settings', 'bi bi-gear') + ]; + } + }); + } + + private updateLinks(): void { if (!this.isDaemonRunning) { this.navLinks = [ new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'), @@ -33,8 +60,6 @@ export class SidebarComponent implements OnChanges { new NavLink('Settings', '/settings', 'bi bi-gear') ]; } - this.isLoading = false; - this.errorMessage = ''; } public isActive(navLink: NavLink): boolean { diff --git a/src/common/DaemonSettings.ts b/src/common/DaemonSettings.ts index 28fdbd4..5e2451d 100644 --- a/src/common/DaemonSettings.ts +++ b/src/common/DaemonSettings.ts @@ -20,7 +20,7 @@ export class DaemonSettings { public testDbgLockSleep: number = 0; public testnet: boolean = false; - public mainnet: boolean = false; + public mainnet: boolean = true; public stagenet: boolean = false; public regtest: boolean = false;