Restart daemon on settings save

This commit is contained in:
everoddandeven 2024-10-09 17:31:38 +02:00
parent 4cea8c06fe
commit d066b7cdaa
23 changed files with 197 additions and 103 deletions

View file

@ -15,7 +15,6 @@ export class DaemonDataService {
private _lastRefreshHeight: number = -1;
private _daemonRunning: boolean = false;
private _daemonRestarting: boolean = false;
private _daemonInfo?: DaemonInfo;
private _gettingDaemonInfo: boolean = false;
@ -97,7 +96,7 @@ export class DaemonDataService {
}
public get restarting(): boolean {
return this._daemonRestarting;
return this.daemonService.restarting;
}
public get refreshing(): boolean {

View file

@ -99,6 +99,7 @@ export class DaemonService {
//private url: string = "https://moneronode.org:18081";
public stopping: boolean = false;
public starting: boolean = false;
public restarting: boolean = false;
public readonly onDaemonStatusChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
public readonly onDaemonStopStart: EventEmitter<void> = new EventEmitter<void>();
public readonly onDaemonStopEnd: EventEmitter<void> = new EventEmitter<void>();
@ -148,10 +149,25 @@ export class DaemonService {
});
}
public async saveSettings(settings: DaemonSettings): Promise<void> {
public async saveSettings(settings: DaemonSettings, restartDaemon: boolean = true): Promise<void> {
const db = await this.openDbPromise;
await db.put(this.storeName, { id: 1, ...settings });
this.settings = settings;
if (restartDaemon) {
const running = await this.isRunning();
if (!running) {
return;
}
try {
await this.restartDaemon();
}
catch(error) {
console.error(error);
}
}
}
public async getSettings(): Promise<DaemonSettings> {
@ -293,6 +309,32 @@ export class DaemonService {
}
public async restartDaemon(): Promise<void> {
this.restarting = true;
let err: any = undefined;
try {
const running = await this.isRunning();
if (!running) {
await this.startDaemon();
}
else {
await this.stopDaemon();
await this.startDaemon();
}
}
catch(error) {
console.error(error);
err = error;
}
this.restarting = false;
if (err) {
throw err;
}
}
private async checkDaemonIsRunning(): Promise<boolean> {
try {
await this.callRpc(new EmptyRpcRequest());
@ -757,6 +799,7 @@ export class DaemonService {
console.warn("Daemon is starting");
return;
}
this.stopping = true;
this.onDaemonStopStart.emit();
@ -771,6 +814,7 @@ export class DaemonService {
for(let i = 0; i < maxChecks; i++) {
if (!await this.isRunning(true)) {
this.stopping = false;
return;
}
await this.delay(5000);

View file

@ -100,4 +100,3 @@
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -491,4 +491,3 @@
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -11,7 +11,7 @@
</div>
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>
<div *ngIf="daemonRunning && !stoppingDaemon" class="tab-content" id="pills-tabContent">
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab" tabindex="0">

View file

@ -41,4 +41,3 @@
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -469,4 +469,3 @@
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -108,4 +108,3 @@
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -216,4 +216,3 @@
</div>
</div>
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -143,4 +143,3 @@
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -39,7 +39,7 @@
</div>
<div class="tab-pane fade" id="pills-rpc" role="tabpanel" aria-labelledby="pills-rpc-tab" tabindex="0">
<div class="row g-5 m-2">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<h4 class="mb-3">General</h4>
<div class="row gy-3">
@ -261,7 +261,7 @@
</div>
<div class="tab-pane fade" id="pills-p2p" role="tabpanel" aria-labelledby="pills-p2p-tab" tabindex="0">
<div class="row g-5 m-2">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<h4 class="mb-3">General</h4>
<div class="row gy-3">
@ -324,7 +324,7 @@
</div>
<div class="tab-pane fade" id="pills-blockchain" role="tabpanel" aria-labelledby="pills-blockchain-tab" tabindex="0">
<div class="row g-5 m-2">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<h4 class="mb-3">Bootstrap Daemon</h4>
<form class="needs-validation" novalidate="">
@ -403,6 +403,13 @@
<br>
<small class="text-body-secondary">Keep alternative blocks on restart</small>
</div>
<div class="form-check form-switch col-md-12">
<label for="sync-on-wifi" class="form-check-label">Sync on Wi-Fi</label>
<input class="form-control form-check-input" type="checkbox" role="switch" id="sync-on-wifi" [checked]="currentSettings.syncOnWifi" [(ngModel)]="currentSettings.syncOnWifi" [ngModelOptions]="{standalone: true}">
<br>
<small class="text-body-secondary">Sync when node is connected to Wi-Fi</small>
</div>
<div class="col-md-4">
<label for="block-sync-size" class="form-label">Block sync size</label>
@ -419,7 +426,26 @@
<input type="text" class="form-control" id="db-sync-mode" placeholder="fast:async:250000000bytes" [(ngModel)]="currentSettings.dbSyncMode" [ngModelOptions]="{standalone: true}">
</div>
<div class="form-check form-switch col-md-12">
<label for="sync-period-enabled" class="form-check-label">Sync Period Enabled</label>
<input class="form-control form-check-input" type="checkbox" role="switch" id="sync-period-enabled" [checked]="currentSettings.syncPeriodEnabled" [(ngModel)]="currentSettings.syncPeriodEnabled" [ngModelOptions]="{standalone: true}" [disabled]="currentSettings.noSync">
<br>
<small class="text-body-secondary">Enable syncing in certain hours</small>
</div>
<div class="col-md-4">
<label for="sync-period-from" class="form-label">From</label>
<div class="cs-form">
<input id="sync-period-from" type="time" class="form-control" value="10:05 AM" [disabled]="!currentSettings.syncPeriodEnabled" [(ngModel)]="currentSettings.syncPeriodFrom" [ngModelOptions]="{standalone: true}" />
</div>
</div>
<div class="col-md-4">
<label for="sync-period-to" class="form-label">To</label>
<div class="cs-form">
<input id="sync-period-to" type="time" class="form-control" value="10:05 AM" [disabled]="!currentSettings.syncPeriodEnabled" [(ngModel)]="currentSettings.syncPeriodTo" [ngModelOptions]="{standalone: true}"/>
</div>
</div>
<hr class="my-4">
@ -447,7 +473,7 @@
</div>
<div class="tab-pane fade" id="pills-mining" role="tabpanel" aria-labelledby="pills-mining-tab" tabindex="0">
<div class="row g-5 m-2">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<form class="needs-validation" novalidate="">
@ -494,7 +520,7 @@
</div>
<div class="tab-pane fade" id="pills-logs" role="tabpanel" aria-labelledby="pills-logs-tab" tabindex="0">
<div class="row g-5 m-2">
<div class="row g-5 p-2">
<div class="col-md-7 col-lg-10">
<form class="needs-validation" novalidate="">
@ -532,6 +558,9 @@
<hr class="my-4">
<button class="w-100 btn btn-primary btn-lg" type="submit" [disabled]="!modified" (click)="OnSave()">Save</button>
<button *ngIf="!savingChanges" class="w-100 btn btn-primary btn-lg" type="submit" [disabled]="saveDisabled" (click)="OnSave()">Save</button>
<button *ngIf="savingChanges" class="w-100 btn btn-primary btn-lg" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Saving changes
</button>
</div>

View file

@ -1,6 +1,4 @@
import { AfterViewInit, Component } from '@angular/core';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { Router } from '@angular/router';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonSettings } from '../../../common/DaemonSettings';
import { DaemonService } from '../../core/services/daemon/daemon.service';
@ -13,25 +11,27 @@ import { DaemonService } from '../../core/services/daemon/daemon.service';
export class SettingsComponent implements AfterViewInit {
public readonly navbarLinks: NavbarLink[];
private originalSettings: DaemonSettings;
public currentSettings: DaemonSettings;
public savingChanges: boolean = false;
public rpcLoginUser: string;
public rpcLoginPassword: string;
public loading: boolean;
public networkType: 'mainnet' | 'testnet' | 'stagenet' = 'mainnet';
constructor(private router: Router, private navbarService: NavbarService, private daemonService: DaemonService) {
constructor(private daemonService: DaemonService) {
this.loading = true;
this.navbarLinks = [
new NavbarLink('pills-general-tab', '#pills-general', 'pills-general', true, 'General'),
new NavbarLink('pills-rpc-tab', '#pills-rpc', 'pills-rpc', false, 'RPC'),
new NavbarLink('pills-p2p-tab', '#pills-p2p', 'pills-p2p', false, 'P2P'),
new NavbarLink('pills-blockchain-tab', '#pills-blockchain', 'pills-blockchain', false, 'Blockchain'),
new NavbarLink('pills-mining-tab', '#pills-mining', 'pills-mining', false, 'Mining'),
new NavbarLink('pills-logs-tab', '#pills-logs', 'pills-logs', false, 'Logs')
new NavbarLink('pills-general-tab', '#pills-general', 'pills-general', true, 'General', false),
new NavbarLink('pills-rpc-tab', '#pills-rpc', 'pills-rpc', false, 'RPC', false),
new NavbarLink('pills-p2p-tab', '#pills-p2p', 'pills-p2p', false, 'P2P', false),
new NavbarLink('pills-blockchain-tab', '#pills-blockchain', 'pills-blockchain', false, 'Blockchain', false),
new NavbarLink('pills-mining-tab', '#pills-mining', 'pills-mining', false, 'Mining', false),
new NavbarLink('pills-logs-tab', '#pills-logs', 'pills-logs', false, 'Logs', false)
];
this.originalSettings = new DaemonSettings();
@ -66,9 +66,12 @@ export class SettingsComponent implements AfterViewInit {
return false;
}
public get saveDisabled(): boolean {
return !this.modified || this.daemonService.restarting || this.daemonService.starting || this.daemonService.stopping;
}
ngAfterViewInit(): void {
this.navbarService.setLinks(this.navbarLinks);
this.navbarService.enableLinks();
}
public OnOfflineChange() {
@ -143,9 +146,18 @@ export class SettingsComponent implements AfterViewInit {
return;
}
await this.daemonService.saveSettings(this.currentSettings);
this.savingChanges = true;
this.originalSettings = this.currentSettings.clone();
try {
await this.daemonService.saveSettings(this.currentSettings);
this.originalSettings = this.currentSettings.clone();
}
catch(error) {
console.error(error);
}
this.savingChanges = false;
}
public chooseMonerodFile(): void {

View file

@ -309,4 +309,3 @@
<app-daemon-not-running></app-daemon-not-running>
<app-daemon-stopping></app-daemon-stopping>

View file

@ -1,17 +1,32 @@
<div *ngIf="!daemonRunning" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
<h2 *ngIf="!startingDaemon && daemonConfigured"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not running</h2>
<h2 *ngIf="!startingDaemon && !daemonConfigured"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not configured</h2>
<p *ngIf="!startingDaemon && daemonConfigured">Start monero daemon</p>
<p *ngIf="!startingDaemon && !daemonConfigured">Configure monero daemon</p>
<div *ngIf="!daemonRunning || stoppingDaemon || restartingDaemon" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
<h2 *ngIf="!daemonRunning && !startingDaemon && !stoppingDaemon && !restartingDaemon && daemonConfigured"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not running</h2>
<h2 *ngIf="!daemonRunning && !startingDaemon && !stoppingDaemon && !restartingDaemon && !daemonConfigured"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not configured</h2>
<h2 *ngIf="restartingDaemon"><i class="bi bi-arrow-clockwise m-4"></i> Daemon restarting</h2>
<h2 *ngIf="stoppingDaemon"><i class="bi bi-stop-fill m-4"></i> Daemon is stopping</h2>
<p *ngIf="!daemonRunning && !startingDaemon && !stoppingDaemon && !restartingDaemon && daemonConfigured">Start monero daemon</p>
<p *ngIf="!startingDaemon && !startingDaemon && !stoppingDaemon && !restartingDaemon && !daemonConfigured">Configure monero daemon</p>
<h2 *ngIf="startingDaemon"><i class="bi bi-play-fill m-4"></i> Daemon is starting</h2>
<p *ngIf="startingDaemon">Starting monero daemon</p>
<p *ngIf="restartingDaemon">Restarting monero daemon</p>
<button *ngIf="!startingDaemon && daemonConfigured" class="btn btn-outline-light" type="button" (click)="startDaemon()"><i class="bi bi-play-fill"></i> Start</button>
<button *ngIf="!startingDaemon && !stoppingDaemon && !restartingDaemon && daemonConfigured" class="btn btn-outline-light" type="button" (click)="startDaemon()"><i class="bi bi-play-fill"></i> Start</button>
<button *ngIf="startingDaemon" class="btn btn-outline-light" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Starting monerod
</button>
<button *ngIf="restartingDaemon" class="btn btn-outline-light" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Restarting monerod
</button>
<button *ngIf="stoppingDaemon" class="btn btn-outline-light" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Stopping monerod
</button>
&nbsp;
<button *ngIf="!startingDaemon" routerLink="/settings" class="btn btn-outline-light" type="button"><i class="bi bi-gear"></i> Configure</button>
<button *ngIf="!startingDaemon && !stoppingDaemon && !restartingDaemon" routerLink="/settings" class="btn btn-outline-light" type="button"><i class="bi bi-gear"></i> Configure</button>
</div>

View file

@ -10,17 +10,21 @@ import { DaemonDataService } from '../../../core/services';
export class DaemonNotRunningComponent {
public get daemonRunning(): boolean {
return this.daemonData.running;
return this.daemonData.running && !this.startingDaemon && !this.stoppingDaemon && !this.restartingDaemon;
}
public daemonConfigured: boolean = true;
public get startingDaemon(): boolean {
return this.daemonService.starting;
return this.daemonService.starting && !this.restartingDaemon;
}
private get stoppingDaemon(): boolean{
return this.daemonData.stopping;
public get stoppingDaemon(): boolean{
return this.daemonData.stopping && !this.restartingDaemon;
}
public get restartingDaemon(): boolean {
return this.daemonService.restarting;
}
constructor(private daemonData: DaemonDataService, private daemonService: DaemonService, private ngZone: NgZone) {
@ -50,6 +54,20 @@ export class DaemonNotRunningComponent {
reject(error);
}
}, 500)});
}
}
public async restartDaemon(): Promise<void> {
await new Promise<void>((resolve, reject) => {
setTimeout(async () => {
try {
await this.daemonService.restartDaemon();
resolve();
}
catch(error) {
console.error(error);
reject(error);
}
}, 500)});
}
}

View file

@ -1,8 +0,0 @@
<div *ngIf="stopping" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
<h2><i class="bi bi-power m-4"></i> Daemon is stopping</h2>
<!--<p>Stopping monero daemon</p>-->
<button class="btn btn-outline-light" type="button" disabled>
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
Stopping daemon
</button>
</div>

View file

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

View file

@ -1,19 +0,0 @@
import { Component } from '@angular/core';
import { DaemonService } from '../../../core/services/daemon/daemon.service';
@Component({
selector: 'app-daemon-stopping',
templateUrl: './daemon-stopping.component.html',
styleUrl: './daemon-stopping.component.scss'
})
export class DaemonStoppingComponent {
public get stopping(): boolean {
return this.daemonService.stopping;
}
constructor(private daemonService: DaemonService) {
}
}

View file

@ -1,4 +1,3 @@
export * from './page-not-found/page-not-found.component';
export * from './sidebar/sidebar.component';
export * from './daemon-not-running/daemon-not-running.component';
export * from './daemon-stopping/daemon-stopping.component';

View file

@ -28,6 +28,10 @@ export class NavbarComponent {
return this.daemonService.stopping;
}
public get restarting(): boolean {
return this.daemonService.restarting;
}
constructor(private navbarService: NavbarService, private daemonService: DaemonService, private ngZone: NgZone) {
this.daemonService.isRunning().then((running: boolean) => {
@ -61,9 +65,7 @@ export class NavbarComponent {
}
public async restartDaemon(): Promise<void> {
await this.stopDaemon();
await this.startDaemon();
await this.daemonService.restartDaemon();
}
public async quit(): Promise<void> {

View file

@ -3,15 +3,15 @@ import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent, DaemonStoppingComponent } from './components/';
import { PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent } from './components/';
import { WebviewDirective } from './directives/';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { NavbarComponent } from './components/navbar/navbar.component';
@NgModule({
declarations: [PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent, DaemonStoppingComponent, NavbarComponent, WebviewDirective],
declarations: [PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent, NavbarComponent, WebviewDirective],
imports: [CommonModule, TranslateModule, FormsModule, RouterModule],
exports: [TranslateModule, WebviewDirective, FormsModule, SidebarComponent, DaemonNotRunningComponent, DaemonStoppingComponent, NavbarComponent]
exports: [TranslateModule, WebviewDirective, FormsModule, SidebarComponent, DaemonNotRunningComponent, NavbarComponent]
})
export class SharedModule {}

View file

@ -1,6 +1,11 @@
export class DaemonSettings {
public monerodPath: string = '';
public syncOnWifi: boolean = true;
public syncPeriodEnabled: boolean = false;
public syncPeriodFrom: any = '00:00';
public syncPeriodTo: any = '00:00';
public logFile: string = '';
public logLevel: number = 0;
public maxLogFileSize: number = 104850000;
@ -131,7 +136,36 @@ export class DaemonSettings {
public disableRpcBan: boolean = false;
public equals(settings: DaemonSettings): boolean {
return this.toCommandOptions().join('') == settings.toCommandOptions().join('');
//return this.toCommandOptions().join('') == settings.toCommandOptions().join('');
return this.deepEqual(this, settings);
}
private deepEqual(obj1: any, obj2: any): boolean {
// Se sono lo stesso riferimento, sono uguali
if (obj1 === obj2) return true;
// Se uno dei due è nullo o non sono oggetti, non sono uguali
if (obj1 == null || obj2 == null || typeof obj1 !== 'object' || typeof obj2 !== 'object') {
return false;
}
// Ottieni tutte le chiavi degli oggetti
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
// Se hanno un numero diverso di chiavi, non sono uguali
if (keys1.length !== keys2.length) return false;
// Controlla che ogni chiave e valore sia equivalente
for (let key of keys1) {
// Se una chiave di obj1 non esiste in obj2, non sono uguali
if (!keys2.includes(key)) return false;
// Se il valore della proprietà non è uguale, effettua un confronto ricorsivo
if (!this.deepEqual(obj1[key], obj2[key])) return false;
}
return true;
}
public clone(): DaemonSettings {