Version component

This commit is contained in:
everoddandeven 2024-09-26 18:45:28 +02:00
parent 91bb85e119
commit 9b28d60acf
17 changed files with 254 additions and 22 deletions

View file

@ -10,6 +10,10 @@ let win: BrowserWindow | null = null;
const args = process.argv.slice(1),
serve = args.some(val => val === '--serve');
function getMonerodPath(): string {
return path.resolve(__dirname, monerodFilePath);
}
function createWindow(): BrowserWindow {
const size = screen.getPrimaryDisplay().workAreaSize;
@ -25,6 +29,7 @@ function createWindow(): BrowserWindow {
allowRunningInsecureContent: (serve),
contextIsolation: false
},
icon: path.join(__dirname, 'assets/icons/favicon.ico')
});
if (serve) {
@ -96,8 +101,20 @@ function execMoneroDaemon(configFilePath: string): ChildProcess {
return monerodProcess;
}
function startMoneroDaemon(commandOptions: string[], logHandler?: (message: string) => void): ChildProcessWithoutNullStreams {
const monerodPath = path.resolve(__dirname, monerodFilePath);
function getMonerodVersion(monerodFilePath: string): void {
const monerodProcess = spawn(getMonerodPath(), [ '--version' ]);
monerodProcess.stdout.on('data', (data) => {
win?.webContents.send('on-monerod-version', `${data}`);
})
monerodProcess.stderr.on('data', (data) => {
win?.webContents.send('on-monerod-version-error', `${data}`);
})
}
function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStreams {
const monerodPath = getMonerodPath();
console.log("Starting monerod daemon with options: " + commandOptions.join(" "));
@ -106,14 +123,14 @@ function startMoneroDaemon(commandOptions: string[], logHandler?: (message: stri
// Gestisci l'output di stdout in streaming
monerodProcess.stdout.on('data', (data) => {
console.log(`monerod stdout: ${data}`);
//console.log(`monerod stdout: ${data}`);
win?.webContents.send('monero-stdout', `${data}`);
// Puoi anche inviare i log all'interfaccia utente tramite IPC
});
// Gestisci gli errori in stderr
monerodProcess.stderr.on('data', (data) => {
console.error(`monerod stderr: ${data}`);
//console.error(`monerod stderr: ${data}`);
win?.webContents.send('monero-stderr', `${data}`);
});
@ -150,10 +167,14 @@ try {
}
});
ipcMain.on('start-monerod', (event, configFilePath: string[], logHandler?: (message: string) => void) => {
startMoneroDaemon(configFilePath, logHandler);
ipcMain.on('start-monerod', (event, configFilePath: string[]) => {
startMoneroDaemon(configFilePath);
})
ipcMain.on('get-monerod-version', (event, configFilePath: string) => {
getMonerodVersion(configFilePath);
});
} catch (e) {
// Catch Error
// throw e;

View file

@ -1,5 +1,5 @@
<app-navbar></app-navbar>
<div class="d-flex">
<div class="d-flex" style="min-height: 100%;">
<app-sidebar [isDaemonRunning]="daemonRunning"></app-sidebar>
<div class="col-md-10">
<router-outlet></router-outlet>

View file

@ -79,6 +79,7 @@ import { resolve } from 'path';
providedIn: 'root'
})
export class DaemonService {
private readonly versionApiUrl: string = 'https://api.github.com/repos/monero-project/monero/releases/latest';
private dbName = 'DaemonSettingsDB';
private storeName = 'settingsStore';
private openDbPromise: Promise<IDBPDatabase>;
@ -185,6 +186,14 @@ export class DaemonService {
await new Promise<void>(f => setTimeout(f, ms));
}
private async get(uri: string): Promise<{[key: string]: any}> {
return await firstValueFrom<{ [key: string]: any }>(this.httpClient.get(`${uri}`,this.headers));
}
private async post(uri: string, params: {[key: string]: any} = {}): Promise<{[key: string]: any}> {
return await firstValueFrom<{ [key: string]: any }>(this.httpClient.post(`${uri}`, params, this.headers));
}
private async callRpc(request: RPCRequest): Promise<{ [key: string]: any }> {
try {
let method: string = '';
@ -196,7 +205,7 @@ export class DaemonService {
method = request.method;
}
const response = await firstValueFrom<{ [key: string]: any }>(this.httpClient.post(`${this.url}/${method}`, request.toDictionary(), this.headers));
const response = await this.post(`${this.url}/${method}`, request.toDictionary());
if (response.error) {
this.raiseRpcError(response.error);
@ -460,10 +469,51 @@ export class DaemonService {
return SyncInfo.parse(response.result);
}
public async getVersion(): Promise<DaemonVersion> {
const response = await this.callRpc(new GetVersionRequest());
public async getLatestVersion(): Promise<DaemonVersion> {
const response = await this.get(this.versionApiUrl);
return DaemonVersion.parse(response.result);
if (typeof response.tag_name != 'string') {
throw new Error("Could not get tag name version");
}
if (typeof response.name != 'string') {
throw new Error("Could not get name version");
}
const nameComponents = response.name.split(",");
if (nameComponents.length == 0) {
throw new Error("Could not get name");
}
const name = nameComponents[0];
return new DaemonVersion(0, true, `Monero '${name}' (${response.tag_name}-release)`);
}
public async getVersion(dontUseRpc: boolean = false): Promise<DaemonVersion> {
if(!dontUseRpc && this.daemonRunning) {
const response = await this.callRpc(new GetVersionRequest());
return DaemonVersion.parse(response.result);
}
else if (dontUseRpc) {
const monerodPath: string = ''; // TO DO get local monerod path
return new Promise<DaemonVersion>((resolve, reject) => {
this.electronService.ipcRenderer.on('on-monerod-version', (event, version: string) => {
resolve(DaemonVersion.parse(version));
});
this.electronService.ipcRenderer.on('on-monerod-version-error', (event, version: string) => {
reject(version);
});
this.electronService.ipcRenderer.send('get-monerod-version', monerodPath);
});
}
throw new Error("Daemon not running");
}
public async getFeeEstimate(): Promise<FeeEstimate> {

View file

@ -28,7 +28,7 @@
</div>
@for(card of cards; track card.header) {
@if(card.loading) {
@if(card.loading && !stoppingDaemon) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;" aria-hidden="true">
<div class="card-header"><strong>{{card.header}}</strong></div>
<div class="card-body">
@ -43,7 +43,7 @@
</div>
</div>
}
@else {
@else if (!stoppingDaemon) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header"><strong>{{card.header}}</strong></div>
<div class="card-body">

View file

@ -150,6 +150,8 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
console.error(error);
this.daemonRunning = false;
}
this.cards = this.createLoadingCards();
this.startingDaemon = false;
}, 500);

View file

@ -1,4 +1,9 @@
<div class="terminal bg-dark text-light p-3 m-4">
<div *ngIf="lines.length == 0" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
<h2><i class="bi bi-exclamation-diamond"></i> No logs</h2>
<p>Start monero daemon to enable session logging</p>
</div>
<div *ngIf="lines.length > 0" class="terminal bg-dark text-light p-3 m-4" #logTerminal>
<div class="terminal-output" id="terminalOutput">
<ng-container *ngFor="let line of lines; trackBy: trackByFn">
<div>{{ line }}</div>

View file

@ -1,4 +1,4 @@
import { AfterViewInit, Component, NgZone } from '@angular/core';
import { AfterViewInit, Component, ElementRef, NgZone, ViewChild } from '@angular/core';
import { LogsService } from './logs.service';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
@ -8,6 +8,7 @@ import { NavbarService } from '../../shared/components/navbar/navbar.service';
styleUrl: './logs.component.scss'
})
export class LogsComponent implements AfterViewInit {
@ViewChild('logTerminal', { read: ElementRef }) public logTerminal?: ElementRef<any>;
constructor(private navbarService: NavbarService, private logsService: LogsService, private ngZone: NgZone) {
this.logsService.onLog.subscribe((message: string) => this.onLog());
@ -18,14 +19,16 @@ export class LogsComponent implements AfterViewInit {
}
private onLog(): void {
if (this.logTerminal) this.logTerminal.nativeElement.scrollTop = this.logTerminal.nativeElement.scrollHeight;
// Scorri automaticamente in basso
setTimeout(() => {
this.ngZone.run(() => {
this.lines;
const terminalOutput = document.getElementById('terminalOutput');
const terminalOutput = <HTMLDivElement | null>document.getElementById('terminalOutput');
if (terminalOutput) {
terminalOutput.style.width = `${window.innerWidth}`;
terminalOutput.scrollTop = terminalOutput.scrollHeight;
console.log(`scrolling from ${terminalOutput.offsetTop} to ${terminalOutput.scrollHeight}`)
terminalOutput.scrollBy(0, terminalOutput.scrollHeight)
}
})

View file

@ -1 +1,27 @@
<p>version works!</p>
<div class="row d-flex">
@for(card of cards; track card.header) {
@if(card.loading) {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;" aria-hidden="true">
<div class="card-header"><strong>{{card.header}}</strong></div>
<div class="card-body">
<p class="card-text placeholder-glow">
<span class="placeholder col-7"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-4"></span>
<span class="placeholder col-6"></span>
<span class="placeholder col-8"></span>
</p>
</div>
</div>
}
@else {
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
<div class="card-header"><strong>{{card.header}}</strong></div>
<div class="card-body">
<h5 class="card-title">{{card.content}}</h5>
</div>
</div>
}
}
</div>

View file

@ -2,6 +2,8 @@ import { AfterViewInit, Component } from '@angular/core';
import { NavbarService } from '../../shared/components/navbar/navbar.service';
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
import { DaemonService } from '../../core/services/daemon/daemon.service';
import { SimpleBootstrapCard } from '../../shared/utils';
import { DaemonVersion } from '../../../common/DaemonVersion';
@Component({
selector: 'app-version',
@ -10,14 +12,50 @@ import { DaemonService } from '../../core/services/daemon/daemon.service';
})
export class VersionComponent implements AfterViewInit {
private readonly links: NavbarLink[];
public cards: SimpleBootstrapCard[];
public currentVersion?: DaemonVersion;
public latestVersion?: DaemonVersion;
constructor(private navbarService: NavbarService, private daemonService: DaemonService) {
this.links = [
new NavbarLink('pills-overview-tab', '#pills-overview', 'pills-overview', true, 'Overview')
];
this.cards = this.createCards();
}
ngAfterViewInit(): void {
this.navbarService.setLinks(this.links);
private createCards(): SimpleBootstrapCard[] {
return [
new SimpleBootstrapCard('Current version', this.currentVersion ? this.currentVersion.fullname : '', this.currentVersion == null),
new SimpleBootstrapCard('Latest version', this.latestVersion ? this.latestVersion.fullname : '', this.latestVersion == null)
];
}
private createErrorCards(): SimpleBootstrapCard[] {
return [
new SimpleBootstrapCard('Current version', 'Error', false),
new SimpleBootstrapCard('Latest version', 'Error', false)
];
}
public ngAfterViewInit(): void {
this.navbarService.setLinks(this.links);
this.load()
.then(() => {
this.cards = this.createCards();
})
.catch((error: any) => {
this.currentVersion = undefined;
this.latestVersion = undefined
this.cards = this.createErrorCards();
});
}
public async load(): Promise<void> {
const version = await this.daemonService.getVersion(true);
const latestVersion = await this.daemonService.getLatestVersion();
this.currentVersion = version;
this.latestVersion = latestVersion;
}
}

View file

@ -1,6 +1,6 @@
<nav class="navbar navbar-expand-lg d-flex justify-content-center py-3 text-bg-dark">
&nbsp;&nbsp;
<a href="/" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none">
<a href="" class="d-flex align-items-center mb-3 mb-md-0 me-md-auto text-white text-decoration-none">
<img class="m-1" src="/assets/icons/monero-symbol-on-white-480.png" width="40" height="40">
<span class="fs-4"><strong>Monero Daemon</strong></span>
</a>

View file

@ -1,3 +1,4 @@
<div class="d-flex flex-column flex-shrink-0 p-3 text-bg-dark" style="width: 280px;">
<ul class="nav nav-pills flex-column mb-auto">

View file

@ -0,0 +1,64 @@
body {
min-height: 100vh;
min-height: -webkit-fill-available;
}
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;
}

View file

@ -32,6 +32,8 @@ export class SidebarComponent implements OnChanges {
private createLightLinks(): NavLink[] {
return [
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
new NavLink('Logs', '/logs', 'bi bi-terminal'),
new NavLink('Version', '/version', 'bi bi-git'),
new NavLink('Settings', '/settings', 'bi bi-gear')
];
}

View file

@ -0,0 +1,11 @@
export class SimpleBootstrapCard {
public header: string;
public content: string;
public loading: boolean;
constructor(header: string, content: string, loading: boolean = false) {
this.header = header;
this.content = content;
this.loading = loading;
}
}

View file

@ -0,0 +1 @@
export { SimpleBootstrapCard } from "./SimpleBootstrapCard";

View file

@ -1,13 +1,19 @@
export class DaemonVersion {
public readonly version: number;
public readonly release: boolean;
public readonly fullname: string;
constructor(version: number, release: boolean) {
constructor(version: number, release: boolean, fullname: string = '') {
this.version = version;
this.release = release;
this.fullname = fullname;
}
public static parse(version: any) {
if (typeof version == 'string') {
return new DaemonVersion(0, false, version);
}
const v: number = version.version;
const release: boolean = version.release;

View file

@ -13,7 +13,9 @@ html, body {
padding: 0;
background-color: #373636;
height: 100%;
min-height: 100vh;
font-family: Arial, Helvetica, sans-serif;
min-height: -webkit-fill-available;
}
html {