mirror of
https://github.com/everoddandeven/monerod-gui.git
synced 2025-01-03 01:19:38 +00:00
Monero installer implementation
This commit is contained in:
parent
68987b9599
commit
7208b5ba9f
33 changed files with 2250 additions and 1272 deletions
|
@ -38,8 +38,7 @@
|
||||||
],
|
],
|
||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/jquery/dist/jquery.min.js",
|
"node_modules/jquery/dist/jquery.min.js",
|
||||||
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js",
|
"node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
|
||||||
"node_modules/bootstrap-table/dist/bootstrap-table.js"
|
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/styles.scss",
|
"src/styles.scss",
|
||||||
|
|
125
app/main.ts
125
app/main.ts
|
@ -4,6 +4,11 @@ import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
|
||||||
|
import * as https from 'https';
|
||||||
|
import { createHash } from 'crypto';
|
||||||
|
import * as tar from 'tar';
|
||||||
|
|
||||||
|
|
||||||
const monerodFilePath: string = "/home/sidney/Documenti/monero-x86_64-linux-gnu-v0.18.3.4/monerod";
|
const monerodFilePath: string = "/home/sidney/Documenti/monero-x86_64-linux-gnu-v0.18.3.4/monerod";
|
||||||
|
|
||||||
let win: BrowserWindow | null = null;
|
let win: BrowserWindow | null = null;
|
||||||
|
@ -138,11 +143,99 @@ function startMoneroDaemon(commandOptions: string[]): ChildProcessWithoutNullStr
|
||||||
monerodProcess.on('close', (code) => {
|
monerodProcess.on('close', (code) => {
|
||||||
console.log(`monerod chiuso con codice: ${code}`);
|
console.log(`monerod chiuso con codice: ${code}`);
|
||||||
win?.webContents.send('monero-stdout', `monerod exited with code: ${code}`);
|
win?.webContents.send('monero-stdout', `monerod exited with code: ${code}`);
|
||||||
|
win?.webContents.send('monero-close', code);
|
||||||
});
|
});
|
||||||
|
|
||||||
return monerodProcess;
|
return monerodProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Funzione per il download
|
||||||
|
const downloadFile = (url: string, destination: string, onProgress: (progress: number) => void): Promise<void> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const file = fs.createWriteStream(destination);
|
||||||
|
https.get(url, (response) => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
const totalBytes = parseInt(response.headers['content-length'] || '0', 10);
|
||||||
|
let downloadedBytes = 0;
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
downloadedBytes += chunk.length;
|
||||||
|
const progress = (downloadedBytes / totalBytes) * 100;
|
||||||
|
onProgress(progress); // Notifica il progresso
|
||||||
|
});
|
||||||
|
|
||||||
|
response.pipe(file);
|
||||||
|
|
||||||
|
file.on('finish', () => {
|
||||||
|
file.close(() => resolve());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to download: ${response.statusCode}`));
|
||||||
|
}
|
||||||
|
}).on('error', (err) => {
|
||||||
|
fs.unlink(destination, () => reject(err));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Funzione per scaricare e verificare l'hash
|
||||||
|
const downloadAndVerifyHash = async (hashUrl: string, fileName: string, filePath: string): Promise<boolean> => {
|
||||||
|
const hashFilePath = path.join(app.getPath('temp'), 'monero_hashes.txt');
|
||||||
|
|
||||||
|
// Scarica il file di hash
|
||||||
|
await downloadFile(hashUrl, hashFilePath, () => {});
|
||||||
|
|
||||||
|
// Leggi il file di hash e cerca l'hash corrispondente
|
||||||
|
const hashContent = fs.readFileSync(hashFilePath, 'utf8');
|
||||||
|
const hashLines = hashContent.split('\n');
|
||||||
|
let expectedHash: string | null = null;
|
||||||
|
|
||||||
|
for (const line of hashLines) {
|
||||||
|
const match = line.match(/^(\w+)\s+(\S+)/);
|
||||||
|
if (match && match[2] === fileName) {
|
||||||
|
expectedHash = match[1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!expectedHash) {
|
||||||
|
throw new Error('Hash not found for the downloaded file.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica l'hash del file scaricato
|
||||||
|
const calculatedHash = await verifyFileHash(filePath);
|
||||||
|
return calculatedHash === expectedHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Funzione per verificare l'hash del file
|
||||||
|
const verifyFileHash = (filePath: string): Promise<string> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const hash = createHash('sha256');
|
||||||
|
const fileStream = fs.createReadStream(filePath);
|
||||||
|
|
||||||
|
fileStream.on('data', (data) => {
|
||||||
|
hash.update(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
fileStream.on('end', () => {
|
||||||
|
resolve(hash.digest('hex'));
|
||||||
|
});
|
||||||
|
|
||||||
|
fileStream.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Funzione per estrarre tar.bz2
|
||||||
|
const extractTarBz2 = (filePath: string, destination: string): Promise<void> => {
|
||||||
|
return tar.x({
|
||||||
|
file: filePath,
|
||||||
|
cwd: destination,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
|
@ -175,6 +268,38 @@ try {
|
||||||
getMonerodVersion(configFilePath);
|
getMonerodVersion(configFilePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Gestione IPC
|
||||||
|
ipcMain.handle('download-monero', async (event, downloadUrl: string, destination: string) => {
|
||||||
|
try {
|
||||||
|
const fileName = path.basename(downloadUrl);
|
||||||
|
const filePath = path.join(destination, fileName);
|
||||||
|
const hashUrl = 'https://www.getmonero.org/downloads/hashes.txt';
|
||||||
|
|
||||||
|
// Inizializza il progresso
|
||||||
|
event.sender.send('download-progress', { progress: 0, status: 'Starting download...' });
|
||||||
|
|
||||||
|
// Scarica il file Monero
|
||||||
|
await downloadFile(downloadUrl, filePath, (progress) => {
|
||||||
|
event.sender.send('download-progress', { progress, status: 'Downloading...' });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Scarica e verifica l'hash
|
||||||
|
event.sender.send('download-progress', { progress: 100, status: 'Verifying hash...' });
|
||||||
|
await downloadAndVerifyHash(hashUrl, fileName, filePath);
|
||||||
|
|
||||||
|
// Estrai il file
|
||||||
|
event.sender.send('download-progress', { progress: 100, status: 'Extracting...' });
|
||||||
|
await extractTarBz2(filePath, destination);
|
||||||
|
|
||||||
|
event.sender.send('download-progress', { progress: 100, status: 'Download and extraction completed successfully.' });
|
||||||
|
} catch (error) {
|
||||||
|
event.sender.send('download-progress', { progress: 0, status: `Error: ${error}` });
|
||||||
|
throw new Error(`Error: ${error}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Catch Error
|
// Catch Error
|
||||||
// throw e;
|
// throw e;
|
||||||
|
|
2990
package-lock.json
generated
2990
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -57,10 +57,13 @@
|
||||||
"bootstrap": "5.3.3",
|
"bootstrap": "5.3.3",
|
||||||
"bootstrap-icons": "1.11.3",
|
"bootstrap-icons": "1.11.3",
|
||||||
"bootstrap-table": "1.23.4",
|
"bootstrap-table": "1.23.4",
|
||||||
|
"crypto": "1.0.1",
|
||||||
"idb": "8.0.0",
|
"idb": "8.0.0",
|
||||||
"jquery": "3.7.1",
|
"jquery": "3.7.1",
|
||||||
"rxjs": "7.8.1",
|
"rxjs": "7.8.1",
|
||||||
|
"tar": "7.4.3",
|
||||||
"tslib": "2.6.2",
|
"tslib": "2.6.2",
|
||||||
|
"unbzip2-stream": "1.4.3",
|
||||||
"zone.js": "0.14.4"
|
"zone.js": "0.14.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -81,6 +84,7 @@
|
||||||
"@types/jest": "29.5.12",
|
"@types/jest": "29.5.12",
|
||||||
"@types/jquery": "3.5.30",
|
"@types/jquery": "3.5.30",
|
||||||
"@types/node": "20.12.7",
|
"@types/node": "20.12.7",
|
||||||
|
"@types/unbzip2-stream": "1.4.3",
|
||||||
"@typescript-eslint/eslint-plugin": "7.7.1",
|
"@typescript-eslint/eslint-plugin": "7.7.1",
|
||||||
"@typescript-eslint/parser": "7.7.1",
|
"@typescript-eslint/parser": "7.7.1",
|
||||||
"conventional-changelog-cli": "4.1.0",
|
"conventional-changelog-cli": "4.1.0",
|
||||||
|
|
|
@ -59,7 +59,6 @@ const httpLoaderFactory = (http: HttpClient): TranslateHttpLoader => new Transl
|
||||||
deps: [HttpClient]
|
deps: [HttpClient]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
NavbarComponent,
|
|
||||||
LoadComponent
|
LoadComponent
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|
|
@ -87,12 +87,16 @@ export class DaemonService {
|
||||||
private daemonRunning?: boolean;
|
private daemonRunning?: boolean;
|
||||||
private url: string = "http://127.0.0.1:28081";
|
private url: string = "http://127.0.0.1:28081";
|
||||||
public settings: DaemonSettings;
|
public settings: DaemonSettings;
|
||||||
|
|
||||||
//private url: string = "http://node2.monerodevs.org:28089";
|
//private url: string = "http://node2.monerodevs.org:28089";
|
||||||
//private url: string = "https://testnet.xmr.ditatompel.com";
|
//private url: string = "https://testnet.xmr.ditatompel.com";
|
||||||
//private url: string = "https://xmr.yemekyedim.com:18081";
|
//private url: string = "https://xmr.yemekyedim.com:18081";
|
||||||
//private url: string = "https://moneronode.org:18081";
|
//private url: string = "https://moneronode.org:18081";
|
||||||
|
public stopping: boolean = false;
|
||||||
|
public starting: boolean = false;
|
||||||
public readonly onDaemonStatusChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
|
public readonly onDaemonStatusChanged: EventEmitter<boolean> = new EventEmitter<boolean>();
|
||||||
|
public readonly onDaemonStopStart: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
public readonly onDaemonStopEnd: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
private readonly headers: { [key: string]: string } = {
|
private readonly headers: { [key: string]: string } = {
|
||||||
"Access-Control-Allow-Headers": "*", // this will allow all CORS requests
|
"Access-Control-Allow-Headers": "*", // this will allow all CORS requests
|
||||||
|
@ -102,6 +106,19 @@ export class DaemonService {
|
||||||
constructor(private httpClient: HttpClient, private electronService: ElectronService) {
|
constructor(private httpClient: HttpClient, private electronService: ElectronService) {
|
||||||
this.openDbPromise = this.openDatabase();
|
this.openDbPromise = this.openDatabase();
|
||||||
this.settings = this.loadSettings();
|
this.settings = this.loadSettings();
|
||||||
|
|
||||||
|
if (this.electronService.isElectron) {
|
||||||
|
this.electronService.ipcRenderer.on('monero-close', (event, code: number | null) => {
|
||||||
|
this.onClose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onClose(): void {
|
||||||
|
this.daemonRunning = false;
|
||||||
|
this.stopping = false;
|
||||||
|
this.onDaemonStatusChanged.emit(false);
|
||||||
|
this.onDaemonStopEnd.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async openDatabase(): Promise<IDBPDatabase> {
|
private async openDatabase(): Promise<IDBPDatabase> {
|
||||||
|
@ -238,6 +255,8 @@ export class DaemonService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.starting = true;
|
||||||
|
|
||||||
console.log("Starting daemon");
|
console.log("Starting daemon");
|
||||||
const settings = await this.getSettings();
|
const settings = await this.getSettings();
|
||||||
this.electronService.ipcRenderer.send('start-monerod', settings.toCommandOptions());
|
this.electronService.ipcRenderer.send('start-monerod', settings.toCommandOptions());
|
||||||
|
@ -254,8 +273,8 @@ export class DaemonService {
|
||||||
this.onDaemonStatusChanged.emit(false);
|
this.onDaemonStatusChanged.emit(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
this.starting = false;
|
||||||
}, 500)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async isRunning(force: boolean = false): Promise<boolean> {
|
public async isRunning(force: boolean = false): Promise<boolean> {
|
||||||
|
@ -662,6 +681,8 @@ export class DaemonService {
|
||||||
console.warn("Daemon not running");
|
console.warn("Daemon not running");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.stopping = true;
|
||||||
|
this.onDaemonStopStart.emit();
|
||||||
|
|
||||||
const response = await this.callRpc(new StopDaemonRequest());
|
const response = await this.callRpc(new StopDaemonRequest());
|
||||||
console.log(response);
|
console.log(response);
|
||||||
|
@ -670,8 +691,13 @@ export class DaemonService {
|
||||||
throw new Error(`Could not stop daemon: ${response.status}`);
|
throw new Error(`Could not stop daemon: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.electronService.isElectron) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.daemonRunning = false;
|
this.daemonRunning = false;
|
||||||
this.onDaemonStatusChanged.emit(false);
|
this.onDaemonStatusChanged.emit(false);
|
||||||
|
this.onDaemonStopEnd.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setLimit(limitDown: number, limitUp: number): Promise<{ limitDown: number, limitUp: number }> {
|
public async setLimit(limitDown: number, limitUp: number): Promise<{ limitDown: number, limitUp: number }> {
|
||||||
|
@ -746,5 +772,9 @@ export class DaemonService {
|
||||||
return await this.update('download', path);
|
return await this.update('download', path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getGuiVersion(): string {
|
||||||
|
return "0.1.0-alpha";
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
export * from './electron/electron.service';
|
export * from './electron/electron.service';
|
||||||
|
export * from './daemon/daemon.service';
|
||||||
|
export { MoneroInstallerService } from './monero-installer/monero-installer.service';
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
import { TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MoneroInstallerService } from './monero-installer.service';
|
||||||
|
|
||||||
|
describe('MoneroInstallerService', () => {
|
||||||
|
let service: MoneroInstallerService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({});
|
||||||
|
service = TestBed.inject(MoneroInstallerService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ElectronService } from '../electron/electron.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MoneroInstallerService {
|
||||||
|
constructor(private electronService: ElectronService) {}
|
||||||
|
|
||||||
|
downloadMonero(downloadUrl: string, destination: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.electronService.ipcRenderer.invoke('download-monero', downloadUrl, destination)
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch((error) => reject(error));
|
||||||
|
|
||||||
|
this.electronService.ipcRenderer.on('download-progress', (event, { progress, status }) => {
|
||||||
|
console.log(`Progress: ${progress}% - ${status}`);
|
||||||
|
// Qui puoi aggiornare lo stato di progresso nel tuo componente
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,3 +13,4 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-daemon-not-running></app-daemon-not-running>
|
<app-daemon-not-running></app-daemon-not-running>
|
||||||
|
<app-daemon-stopping></app-daemon-stopping>
|
|
@ -11,8 +11,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<app-daemon-not-running></app-daemon-not-running>
|
<app-daemon-not-running></app-daemon-not-running>
|
||||||
|
<app-daemon-stopping></app-daemon-stopping>
|
||||||
|
|
||||||
<div *ngIf="daemonRunning" class="tab-content" id="pills-tabContent">
|
<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">
|
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab" tabindex="0">
|
||||||
<div class="row d-flex justify-content-center">
|
<div class="row d-flex justify-content-center">
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { DaemonInfo } from '../../../common/DaemonInfo';
|
||||||
import * as $ from 'jquery';
|
import * as $ from 'jquery';
|
||||||
import * as bootstrapTable from 'bootstrap-table';
|
import * as bootstrapTable from 'bootstrap-table';
|
||||||
import { LogsService } from '../logs/logs.service';
|
import { LogsService } from '../logs/logs.service';
|
||||||
|
import { ElectronService } from '../../core/services';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-detail',
|
selector: 'app-detail',
|
||||||
|
@ -52,7 +53,10 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
public cards: Card[];
|
public cards: Card[];
|
||||||
|
|
||||||
constructor(private router: Router,private daemonService: DaemonService, private navbarService: NavbarService, private logsService: LogsService, private ngZone: NgZone) {
|
constructor(
|
||||||
|
private router: Router,private daemonService: DaemonService,
|
||||||
|
private navbarService: NavbarService, private logsService: LogsService,
|
||||||
|
private ngZone: NgZone, private electronService: ElectronService) {
|
||||||
this.daemonRunning = false;
|
this.daemonRunning = false;
|
||||||
this.startingDaemon = false;
|
this.startingDaemon = false;
|
||||||
this.stoppingDaemon = false;
|
this.stoppingDaemon = false;
|
||||||
|
@ -91,6 +95,9 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => {
|
this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => {
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
this.daemonRunning = running;
|
this.daemonRunning = running;
|
||||||
|
if (!running && this.stoppingDaemon) {
|
||||||
|
this.stoppingDaemon = false;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -174,13 +181,13 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
await this.daemonService.stopDaemon();
|
await this.daemonService.stopDaemon();
|
||||||
|
|
||||||
this.daemonRunning = false;
|
if(!this.electronService.isElectron) this.daemonRunning = false;
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.stoppingDaemon = false;
|
if(!this.electronService.isElectron) this.stoppingDaemon = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onNavigationEnd(): void {
|
private onNavigationEnd(): void {
|
||||||
|
@ -208,10 +215,10 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCards(): Card[] {
|
private createCards(): Card[] {
|
||||||
if (!this.daemonRunning) {
|
if (!this.daemonRunning && !this.daemonService.starting) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
if (this.isLoading) {
|
if (this.isLoading || this.daemonService.starting) {
|
||||||
return this.createLoadingCards();
|
return this.createLoadingCards();
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
|
|
|
@ -7,14 +7,33 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="daemonRunning" class="row d-flex justify-content-center">
|
<div *ngIf="daemonRunning" class="row d-flex justify-content-center">
|
||||||
@for(card of cards; track card.header) {
|
@if(!loading) {
|
||||||
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
|
@for(card of cards; track card.header) {
|
||||||
<div class="card-header">{{card.header}}</div>
|
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
|
||||||
<div class="card-body">
|
<div class="card-header">{{card.header}}</div>
|
||||||
<h5 class="card-title">{{card.content}}</h5>
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">{{card.content}}</h5>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
|
}
|
||||||
|
@else {
|
||||||
|
@for(card of cards; track card.header) {
|
||||||
|
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
|
||||||
|
<div class="card-header">{{card.header}}</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>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-daemon-not-running></app-daemon-not-running>
|
<app-daemon-not-running></app-daemon-not-running>
|
||||||
|
<app-daemon-stopping></app-daemon-stopping>
|
|
@ -2,6 +2,7 @@ import { AfterViewInit, Component, NgZone } from '@angular/core';
|
||||||
import { DaemonService } from '../../core/services/daemon/daemon.service';
|
import { DaemonService } from '../../core/services/daemon/daemon.service';
|
||||||
import { NavigationEnd, Router } from '@angular/router';
|
import { NavigationEnd, Router } from '@angular/router';
|
||||||
import { NavbarService } from '../../shared/components/navbar/navbar.service';
|
import { NavbarService } from '../../shared/components/navbar/navbar.service';
|
||||||
|
import { SimpleBootstrapCard } from '../../shared/utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hard-fork-info',
|
selector: 'app-hard-fork-info',
|
||||||
|
@ -9,7 +10,7 @@ import { NavbarService } from '../../shared/components/navbar/navbar.service';
|
||||||
styleUrl: './hard-fork-info.component.scss'
|
styleUrl: './hard-fork-info.component.scss'
|
||||||
})
|
})
|
||||||
export class HardForkInfoComponent implements AfterViewInit {
|
export class HardForkInfoComponent implements AfterViewInit {
|
||||||
public cards: Card[];
|
public cards: SimpleBootstrapCard[];
|
||||||
private earliestHeight: number;
|
private earliestHeight: number;
|
||||||
private enabled: boolean;
|
private enabled: boolean;
|
||||||
private threshold: number;
|
private threshold: number;
|
||||||
|
@ -20,6 +21,8 @@ export class HardForkInfoComponent implements AfterViewInit {
|
||||||
|
|
||||||
public daemonRunning: boolean;
|
public daemonRunning: boolean;
|
||||||
|
|
||||||
|
public loading: boolean = false;
|
||||||
|
|
||||||
constructor(private router: Router, private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) {
|
constructor(private router: Router, private daemonService: DaemonService, private navbarService: NavbarService, private ngZone: NgZone) {
|
||||||
this.cards = [];
|
this.cards = [];
|
||||||
this.enabled = false;
|
this.enabled = false;
|
||||||
|
@ -65,36 +68,37 @@ export class HardForkInfoComponent implements AfterViewInit {
|
||||||
if (!await this.daemonService.isRunning()) {
|
if (!await this.daemonService.isRunning()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const info = await this.daemonService.hardForkInfo();
|
this.cards = this.createCards();
|
||||||
|
|
||||||
this.earliestHeight = info.earliestHeight;
|
this.loading = true;
|
||||||
this.threshold = info.threshold;
|
|
||||||
this.blockVersion = info.version;
|
try {
|
||||||
this.votes = info.votes;
|
const info = await this.daemonService.hardForkInfo();
|
||||||
this.voting = info.voting;
|
|
||||||
this.window = info.window;
|
this.earliestHeight = info.earliestHeight;
|
||||||
|
this.threshold = info.threshold;
|
||||||
|
this.blockVersion = info.version;
|
||||||
|
this.votes = info.votes;
|
||||||
|
this.voting = info.voting;
|
||||||
|
this.window = info.window;
|
||||||
|
}
|
||||||
|
catch(error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createCards(): Card[] {
|
private createCards(): SimpleBootstrapCard[] {
|
||||||
return [
|
return [
|
||||||
new Card('Status', this.enabled ? 'enabled' : 'disabled'),
|
new SimpleBootstrapCard('Status', this.enabled ? 'enabled' : 'disabled'),
|
||||||
new Card('Earliest height', `${this.earliestHeight}`),
|
new SimpleBootstrapCard('Earliest height', `${this.earliestHeight}`),
|
||||||
new Card('Threshold', `${this.threshold}`),
|
new SimpleBootstrapCard('Threshold', `${this.threshold}`),
|
||||||
new Card('Block version', `${this.blockVersion}`),
|
new SimpleBootstrapCard('Block version', `${this.blockVersion}`),
|
||||||
new Card('Votes', `${this.votes}`),
|
new SimpleBootstrapCard('Votes', `${this.votes}`),
|
||||||
new Card('Voting', `${this.voting}`),
|
new SimpleBootstrapCard('Voting', `${this.voting}`),
|
||||||
new Card('Window', `${this.window}`)
|
new SimpleBootstrapCard('Window', `${this.window}`)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Card {
|
|
||||||
public header: string;
|
|
||||||
public content: string;
|
|
||||||
|
|
||||||
constructor(header: string, content: string) {
|
|
||||||
this.header = header;
|
|
||||||
this.content = content;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,18 +18,22 @@ export class LogsComponent implements AfterViewInit {
|
||||||
return this.logsService.lines;
|
return this.logsService.lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scrollToBottom(): void {
|
||||||
|
this.lines;
|
||||||
|
const terminalOutput = <HTMLDivElement | null>document.getElementById('terminalOutput');
|
||||||
|
if (terminalOutput) {
|
||||||
|
terminalOutput.style.width = `${window.innerWidth}`;
|
||||||
|
console.log(`scrolling from ${terminalOutput.offsetTop} to ${terminalOutput.scrollHeight}`)
|
||||||
|
terminalOutput.scrollBy(0, terminalOutput.scrollHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private onLog(): void {
|
private onLog(): void {
|
||||||
if (this.logTerminal) this.logTerminal.nativeElement.scrollTop = this.logTerminal.nativeElement.scrollHeight;
|
if (this.logTerminal) this.logTerminal.nativeElement.scrollTop = this.logTerminal.nativeElement.scrollHeight;
|
||||||
// Scorri automaticamente in basso
|
// Scorri automaticamente in basso
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
this.lines;
|
this.scrollToBottom();
|
||||||
const terminalOutput = <HTMLDivElement | null>document.getElementById('terminalOutput');
|
|
||||||
if (terminalOutput) {
|
|
||||||
terminalOutput.style.width = `${window.innerWidth}`;
|
|
||||||
console.log(`scrolling from ${terminalOutput.offsetTop} to ${terminalOutput.scrollHeight}`)
|
|
||||||
terminalOutput.scrollBy(0, terminalOutput.scrollHeight)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
}, 100);
|
}, 100);
|
||||||
|
@ -41,5 +45,6 @@ export class LogsComponent implements AfterViewInit {
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.navbarService.removeLinks();
|
this.navbarService.removeLinks();
|
||||||
|
this.scrollToBottom();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ElectronService } from '../../core/services';
|
||||||
export class LogsService {
|
export class LogsService {
|
||||||
public readonly onLog: EventEmitter<string> = new EventEmitter<string>();
|
public readonly onLog: EventEmitter<string> = new EventEmitter<string>();
|
||||||
public readonly lines: string[] = [];
|
public readonly lines: string[] = [];
|
||||||
|
private readonly ansiRegex: RegExp = /\u001b\[[0-9;]*m/g;
|
||||||
|
|
||||||
constructor(private electronService: ElectronService, private ngZone: NgZone) {
|
constructor(private electronService: ElectronService, private ngZone: NgZone) {
|
||||||
if (this.electronService.isElectron) {
|
if (this.electronService.isElectron) {
|
||||||
|
@ -17,10 +18,14 @@ export class LogsService {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public cleanLog(message: string): string {
|
||||||
|
return message.replace(this.ansiRegex, '').replace(/[\r\n]+/g, '\n').trim();
|
||||||
|
}
|
||||||
|
|
||||||
public log(message: string): void {
|
public log(message: string): void {
|
||||||
this.ngZone.run(() => {
|
this.ngZone.run(() => {
|
||||||
this.lines.push(message);
|
this.lines.push(this.cleanLog(message));
|
||||||
this.onLog.emit(message);
|
this.onLog.emit(this.cleanLog(message));
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,4 +83,5 @@
|
||||||
-->
|
-->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-daemon-not-running></app-daemon-not-running>
|
<app-daemon-not-running></app-daemon-not-running>
|
||||||
|
<app-daemon-stopping></app-daemon-stopping>
|
|
@ -11,4 +11,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-daemon-not-running></app-daemon-not-running>
|
<app-daemon-not-running></app-daemon-not-running>
|
||||||
|
<app-daemon-stopping></app-daemon-stopping>
|
|
@ -164,4 +164,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<app-daemon-not-running></app-daemon-not-running>
|
<app-daemon-not-running></app-daemon-not-running>
|
||||||
|
<app-daemon-stopping></app-daemon-stopping>
|
|
@ -38,4 +38,8 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-4">
|
||||||
|
|
||||||
|
<button class="w-100 btn btn-primary btn-lg" type="submit" (click)="upgrade()">Upgrade</button>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { NavbarLink } from '../../shared/components/navbar/navbar.model';
|
||||||
import { DaemonService } from '../../core/services/daemon/daemon.service';
|
import { DaemonService } from '../../core/services/daemon/daemon.service';
|
||||||
import { SimpleBootstrapCard } from '../../shared/utils';
|
import { SimpleBootstrapCard } from '../../shared/utils';
|
||||||
import { DaemonVersion } from '../../../common/DaemonVersion';
|
import { DaemonVersion } from '../../../common/DaemonVersion';
|
||||||
import { ElectronService } from '../../core/services';
|
import { ElectronService, MoneroInstallerService } from '../../core/services';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-version',
|
selector: 'app-version',
|
||||||
|
@ -17,7 +17,9 @@ export class VersionComponent implements AfterViewInit {
|
||||||
public currentVersion?: DaemonVersion;
|
public currentVersion?: DaemonVersion;
|
||||||
public latestVersion?: DaemonVersion;
|
public latestVersion?: DaemonVersion;
|
||||||
|
|
||||||
constructor(private navbarService: NavbarService, private daemonService: DaemonService, private electronService: ElectronService) {
|
public downloadPath: string = '/home/sidney/monerod/';
|
||||||
|
|
||||||
|
constructor(private navbarService: NavbarService, private daemonService: DaemonService, private electronService: ElectronService, private moneroInstaller: MoneroInstallerService) {
|
||||||
this.links = [
|
this.links = [
|
||||||
new NavbarLink('pills-overview-tab', '#pills-overview', 'pills-overview', true, 'Overview')
|
new NavbarLink('pills-overview-tab', '#pills-overview', 'pills-overview', true, 'Overview')
|
||||||
];
|
];
|
||||||
|
@ -26,15 +28,17 @@ export class VersionComponent implements AfterViewInit {
|
||||||
|
|
||||||
private createCards(): SimpleBootstrapCard[] {
|
private createCards(): SimpleBootstrapCard[] {
|
||||||
return [
|
return [
|
||||||
new SimpleBootstrapCard('Current version', this.currentVersion ? this.currentVersion.fullname : '', this.currentVersion == null),
|
new SimpleBootstrapCard('GUI Version', this.daemonService.getGuiVersion()),
|
||||||
new SimpleBootstrapCard('Latest version', this.latestVersion ? this.latestVersion.fullname : '', this.latestVersion == null)
|
new SimpleBootstrapCard('Current Monerod version', this.currentVersion ? this.currentVersion.fullname : '', this.currentVersion == null),
|
||||||
|
new SimpleBootstrapCard('Latest Monerod version', this.latestVersion ? this.latestVersion.fullname : '', this.latestVersion == null)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private createErrorCards(): SimpleBootstrapCard[] {
|
private createErrorCards(): SimpleBootstrapCard[] {
|
||||||
return [
|
return [
|
||||||
new SimpleBootstrapCard('Current version', 'Error', false),
|
new SimpleBootstrapCard('GUI Version', this.daemonService.getGuiVersion()),
|
||||||
new SimpleBootstrapCard('Latest version', 'Error', false)
|
new SimpleBootstrapCard('Current Monerod version', 'Error', false),
|
||||||
|
new SimpleBootstrapCard('Latest Monerod version', 'Error', false)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,4 +63,21 @@ export class VersionComponent implements AfterViewInit {
|
||||||
this.latestVersion = latestVersion;
|
this.latestVersion = latestVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public downloadProgress: number = 100;
|
||||||
|
public downloadStatus : string = '';
|
||||||
|
|
||||||
|
public async upgrade(): Promise<void> {
|
||||||
|
|
||||||
|
const downloadUrl = 'https://downloads.getmonero.org/cli/linux64'; // Cambia in base al sistema
|
||||||
|
const destination = '/home/sidney/'; // Aggiorna con il percorso desiderato
|
||||||
|
|
||||||
|
this.moneroInstaller.downloadMonero(downloadUrl, destination)
|
||||||
|
.then(() => {
|
||||||
|
console.log('Download completato con successo.');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Errore:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<div *ngIf="!daemonRunning" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
|
<div *ngIf="!daemonRunning" class="h-100 p-5 text-bg-dark rounded-3 m-4 text-center">
|
||||||
<h2><i class="bi bi-exclamation-diamond m-4"></i> Daemon not running</h2>
|
<h2 *ngIf="!startingDaemon"><i class="bi bi-exclamation-diamond m-4"></i> Daemon not running</h2>
|
||||||
<p>Start monero daemon</p>
|
<p *ngIf="!startingDaemon">Start 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>
|
||||||
|
|
||||||
<button *ngIf="!startingDaemon" 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" (click)="startDaemon()"><i class="bi bi-play-fill"></i> Start</button>
|
||||||
<button *ngIf="startingDaemon" class="btn btn-outline-light" type="button" disabled>
|
<button *ngIf="startingDaemon" class="btn btn-outline-light" type="button" disabled>
|
||||||
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
|
||||||
Starting daemon
|
Starting monerod
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button routerLink="/settings" class="btn btn-outline-light" type="button" [disabled]="startingDaemon"><i class="bi bi-gear"></i> Configure</button>
|
<button *ngIf="!startingDaemon" routerLink="/settings" class="btn btn-outline-light" type="button"><i class="bi bi-gear"></i> Configure</button>
|
||||||
</div>
|
</div>
|
|
@ -9,7 +9,11 @@ import { DaemonService } from '../../../core/services/daemon/daemon.service';
|
||||||
export class DaemonNotRunningComponent {
|
export class DaemonNotRunningComponent {
|
||||||
|
|
||||||
public daemonRunning: boolean = false;
|
public daemonRunning: boolean = false;
|
||||||
public startingDaemon: boolean = false;
|
|
||||||
|
public get startingDaemon(): boolean {
|
||||||
|
return this.daemonService.starting;
|
||||||
|
}
|
||||||
|
|
||||||
private stoppingDaemon: boolean = false;
|
private stoppingDaemon: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
@ -33,8 +37,6 @@ export class DaemonNotRunningComponent {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startingDaemon = true;
|
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
await this.daemonService.startDaemon();
|
await this.daemonService.startDaemon();
|
||||||
|
@ -44,8 +46,6 @@ export class DaemonNotRunningComponent {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.daemonRunning = false;
|
this.daemonRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.startingDaemon = false;
|
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<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>
|
|
@ -0,0 +1,23 @@
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './page-not-found/page-not-found.component';
|
export * from './page-not-found/page-not-found.component';
|
||||||
export * from './sidebar/sidebar.component';
|
export * from './sidebar/sidebar.component';
|
||||||
export * from './daemon-not-running/daemon-not-running.component';
|
export * from './daemon-not-running/daemon-not-running.component';
|
||||||
|
export * from './daemon-stopping/daemon-stopping.component';
|
||||||
|
|
|
@ -23,6 +23,29 @@
|
||||||
<strong>Monero Daemon</strong>
|
<strong>Monero Daemon</strong>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<ul class="navbar-nav flex-row">
|
||||||
|
<li *ngIf="!running && !stopping && !starting" class="nav-item text-nowrap">
|
||||||
|
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="collapse" aria-expanded="false" aria-label="Start daemon">
|
||||||
|
<i class="bi bi-play-fill"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li *ngIf="running && !stopping && !starting" class="nav-item text-nowrap">
|
||||||
|
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="collapse" aria-expanded="false" aria-label="Stop daemon">
|
||||||
|
<i class="bi bi-stop-fill"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li *ngIf="running && !stopping && !starting" class="nav-item text-nowrap">
|
||||||
|
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="collapse" aria-expanded="false" aria-label="Restart daemon">
|
||||||
|
<i class="bi bi-arrow-clockwise"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item text-nowrap">
|
||||||
|
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="offcanvas" aria-label="Quit" [disabled]="stopping || starting">
|
||||||
|
<i class="bi bi-power"></i>
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<ul class="navbar-nav flex-row d-md-none">
|
<ul class="navbar-nav flex-row d-md-none">
|
||||||
<li class="nav-item text-nowrap">
|
<li class="nav-item text-nowrap">
|
||||||
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSearch" aria-controls="navbarSearch" aria-expanded="false" aria-label="Toggle search">
|
<button class="nav-link px-3 text-white" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSearch" aria-controls="navbarSearch" aria-expanded="false" aria-label="Toggle search">
|
||||||
|
|
|
@ -1,21 +1,46 @@
|
||||||
import { Component } from '@angular/core';
|
import { Component, NgZone } from '@angular/core';
|
||||||
import { NavbarService } from './navbar.service';
|
import { NavbarService } from './navbar.service';
|
||||||
import { NavbarLink } from './navbar.model';
|
import { NavbarLink } from './navbar.model';
|
||||||
|
import { DaemonService } from '../../../core/services/daemon/daemon.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-navbar',
|
selector: 'app-navbar',
|
||||||
standalone: true,
|
|
||||||
imports: [],
|
|
||||||
templateUrl: './navbar.component.html',
|
templateUrl: './navbar.component.html',
|
||||||
styleUrl: './navbar.component.scss'
|
styleUrl: './navbar.component.scss'
|
||||||
})
|
})
|
||||||
export class NavbarComponent {
|
export class NavbarComponent {
|
||||||
|
|
||||||
|
private _running: boolean = false;
|
||||||
|
|
||||||
public get navbarLinks(): NavbarLink[] {
|
public get navbarLinks(): NavbarLink[] {
|
||||||
return this.navbarService.links;
|
return this.navbarService.links;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private navbarService: NavbarService) {
|
public get running(): boolean {
|
||||||
|
return this._running;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get starting(): boolean {
|
||||||
|
return this.daemonService.starting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get stopping(): boolean {
|
||||||
|
return this.daemonService.stopping;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private navbarService: NavbarService, private daemonService: DaemonService, private ngZone: NgZone) {
|
||||||
|
|
||||||
|
this.daemonService.isRunning().then((running: boolean) => {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this._running = running;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.daemonService.onDaemonStatusChanged.subscribe((running: boolean) => {
|
||||||
|
this.ngZone.run(() => {
|
||||||
|
this._running = running;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,15 @@ import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent } from './components/';
|
import { PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent, DaemonStoppingComponent } from './components/';
|
||||||
import { WebviewDirective } from './directives/';
|
import { WebviewDirective } from './directives/';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { NavbarComponent } from './components/navbar/navbar.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent, WebviewDirective],
|
declarations: [PageNotFoundComponent, SidebarComponent, DaemonNotRunningComponent, DaemonStoppingComponent, NavbarComponent, WebviewDirective],
|
||||||
imports: [CommonModule, TranslateModule, FormsModule, RouterModule],
|
imports: [CommonModule, TranslateModule, FormsModule, RouterModule],
|
||||||
exports: [TranslateModule, WebviewDirective, FormsModule, SidebarComponent, DaemonNotRunningComponent]
|
exports: [TranslateModule, WebviewDirective, FormsModule, SidebarComponent, DaemonNotRunningComponent, DaemonStoppingComponent, NavbarComponent]
|
||||||
})
|
})
|
||||||
export class SharedModule {}
|
export class SharedModule {}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": false,
|
"declaration": false,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"target": "es2015",
|
"target": "es2015",
|
||||||
"types": [
|
"types": [
|
||||||
|
|
Loading…
Reference in a new issue