mirror of
https://github.com/everoddandeven/monerod-gui.git
synced 2025-01-03 17:39:49 +00:00
Version component
This commit is contained in:
parent
91bb85e119
commit
9b28d60acf
17 changed files with 254 additions and 22 deletions
33
app/main.ts
33
app/main.ts
|
@ -10,6 +10,10 @@ let win: BrowserWindow | null = null;
|
||||||
const args = process.argv.slice(1),
|
const args = process.argv.slice(1),
|
||||||
serve = args.some(val => val === '--serve');
|
serve = args.some(val => val === '--serve');
|
||||||
|
|
||||||
|
function getMonerodPath(): string {
|
||||||
|
return path.resolve(__dirname, monerodFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
function createWindow(): BrowserWindow {
|
function createWindow(): BrowserWindow {
|
||||||
|
|
||||||
const size = screen.getPrimaryDisplay().workAreaSize;
|
const size = screen.getPrimaryDisplay().workAreaSize;
|
||||||
|
@ -25,6 +29,7 @@ function createWindow(): BrowserWindow {
|
||||||
allowRunningInsecureContent: (serve),
|
allowRunningInsecureContent: (serve),
|
||||||
contextIsolation: false
|
contextIsolation: false
|
||||||
},
|
},
|
||||||
|
icon: path.join(__dirname, 'assets/icons/favicon.ico')
|
||||||
});
|
});
|
||||||
|
|
||||||
if (serve) {
|
if (serve) {
|
||||||
|
@ -96,8 +101,20 @@ function execMoneroDaemon(configFilePath: string): ChildProcess {
|
||||||
return monerodProcess;
|
return monerodProcess;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startMoneroDaemon(commandOptions: string[], logHandler?: (message: string) => void): ChildProcessWithoutNullStreams {
|
function getMonerodVersion(monerodFilePath: string): void {
|
||||||
const monerodPath = path.resolve(__dirname, monerodFilePath);
|
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(" "));
|
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
|
// Gestisci l'output di stdout in streaming
|
||||||
monerodProcess.stdout.on('data', (data) => {
|
monerodProcess.stdout.on('data', (data) => {
|
||||||
console.log(`monerod stdout: ${data}`);
|
//console.log(`monerod stdout: ${data}`);
|
||||||
win?.webContents.send('monero-stdout', `${data}`);
|
win?.webContents.send('monero-stdout', `${data}`);
|
||||||
// Puoi anche inviare i log all'interfaccia utente tramite IPC
|
// Puoi anche inviare i log all'interfaccia utente tramite IPC
|
||||||
});
|
});
|
||||||
|
|
||||||
// Gestisci gli errori in stderr
|
// Gestisci gli errori in stderr
|
||||||
monerodProcess.stderr.on('data', (data) => {
|
monerodProcess.stderr.on('data', (data) => {
|
||||||
console.error(`monerod stderr: ${data}`);
|
//console.error(`monerod stderr: ${data}`);
|
||||||
win?.webContents.send('monero-stderr', `${data}`);
|
win?.webContents.send('monero-stderr', `${data}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -150,10 +167,14 @@ try {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('start-monerod', (event, configFilePath: string[], logHandler?: (message: string) => void) => {
|
ipcMain.on('start-monerod', (event, configFilePath: string[]) => {
|
||||||
startMoneroDaemon(configFilePath, logHandler);
|
startMoneroDaemon(configFilePath);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ipcMain.on('get-monerod-version', (event, configFilePath: string) => {
|
||||||
|
getMonerodVersion(configFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Catch Error
|
// Catch Error
|
||||||
// throw e;
|
// throw e;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<app-navbar></app-navbar>
|
<app-navbar></app-navbar>
|
||||||
<div class="d-flex">
|
<div class="d-flex" style="min-height: 100%;">
|
||||||
<app-sidebar [isDaemonRunning]="daemonRunning"></app-sidebar>
|
<app-sidebar [isDaemonRunning]="daemonRunning"></app-sidebar>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
|
|
|
@ -79,6 +79,7 @@ import { resolve } from 'path';
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class DaemonService {
|
export class DaemonService {
|
||||||
|
private readonly versionApiUrl: string = 'https://api.github.com/repos/monero-project/monero/releases/latest';
|
||||||
private dbName = 'DaemonSettingsDB';
|
private dbName = 'DaemonSettingsDB';
|
||||||
private storeName = 'settingsStore';
|
private storeName = 'settingsStore';
|
||||||
private openDbPromise: Promise<IDBPDatabase>;
|
private openDbPromise: Promise<IDBPDatabase>;
|
||||||
|
@ -185,6 +186,14 @@ export class DaemonService {
|
||||||
await new Promise<void>(f => setTimeout(f, ms));
|
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 }> {
|
private async callRpc(request: RPCRequest): Promise<{ [key: string]: any }> {
|
||||||
try {
|
try {
|
||||||
let method: string = '';
|
let method: string = '';
|
||||||
|
@ -196,7 +205,7 @@ export class DaemonService {
|
||||||
method = request.method;
|
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) {
|
if (response.error) {
|
||||||
this.raiseRpcError(response.error);
|
this.raiseRpcError(response.error);
|
||||||
|
@ -460,10 +469,51 @@ export class DaemonService {
|
||||||
return SyncInfo.parse(response.result);
|
return SyncInfo.parse(response.result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getVersion(): Promise<DaemonVersion> {
|
public async getLatestVersion(): Promise<DaemonVersion> {
|
||||||
const response = await this.callRpc(new GetVersionRequest());
|
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> {
|
public async getFeeEstimate(): Promise<FeeEstimate> {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@for(card of cards; track card.header) {
|
@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 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-header"><strong>{{card.header}}</strong></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@else {
|
@else if (!stoppingDaemon) {
|
||||||
<div class="card text-bg-dark m-3 text-center" style="max-width: 18rem;">
|
<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-header"><strong>{{card.header}}</strong></div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
|
@ -150,6 +150,8 @@ export class DetailComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
this.daemonRunning = false;
|
this.daemonRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.cards = this.createLoadingCards();
|
||||||
|
|
||||||
this.startingDaemon = false;
|
this.startingDaemon = false;
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
|
@ -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">
|
<div class="terminal-output" id="terminalOutput">
|
||||||
<ng-container *ngFor="let line of lines; trackBy: trackByFn">
|
<ng-container *ngFor="let line of lines; trackBy: trackByFn">
|
||||||
<div>{{ line }}</div>
|
<div>{{ line }}</div>
|
||||||
|
|
|
@ -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 { LogsService } from './logs.service';
|
||||||
import { NavbarService } from '../../shared/components/navbar/navbar.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'
|
styleUrl: './logs.component.scss'
|
||||||
})
|
})
|
||||||
export class LogsComponent implements AfterViewInit {
|
export class LogsComponent implements AfterViewInit {
|
||||||
|
@ViewChild('logTerminal', { read: ElementRef }) public logTerminal?: ElementRef<any>;
|
||||||
|
|
||||||
constructor(private navbarService: NavbarService, private logsService: LogsService, private ngZone: NgZone) {
|
constructor(private navbarService: NavbarService, private logsService: LogsService, private ngZone: NgZone) {
|
||||||
this.logsService.onLog.subscribe((message: string) => this.onLog());
|
this.logsService.onLog.subscribe((message: string) => this.onLog());
|
||||||
|
@ -18,14 +19,16 @@ export class LogsComponent implements AfterViewInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onLog(): void {
|
private onLog(): void {
|
||||||
|
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.lines;
|
||||||
const terminalOutput = document.getElementById('terminalOutput');
|
const terminalOutput = <HTMLDivElement | null>document.getElementById('terminalOutput');
|
||||||
if (terminalOutput) {
|
if (terminalOutput) {
|
||||||
terminalOutput.style.width = `${window.innerWidth}`;
|
terminalOutput.style.width = `${window.innerWidth}`;
|
||||||
terminalOutput.scrollTop = terminalOutput.scrollHeight;
|
console.log(`scrolling from ${terminalOutput.offsetTop} to ${terminalOutput.scrollHeight}`)
|
||||||
|
terminalOutput.scrollBy(0, terminalOutput.scrollHeight)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -2,6 +2,8 @@ import { AfterViewInit, Component } from '@angular/core';
|
||||||
import { NavbarService } from '../../shared/components/navbar/navbar.service';
|
import { NavbarService } from '../../shared/components/navbar/navbar.service';
|
||||||
import { NavbarLink } from '../../shared/components/navbar/navbar.model';
|
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 { DaemonVersion } from '../../../common/DaemonVersion';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-version',
|
selector: 'app-version',
|
||||||
|
@ -10,14 +12,50 @@ import { DaemonService } from '../../core/services/daemon/daemon.service';
|
||||||
})
|
})
|
||||||
export class VersionComponent implements AfterViewInit {
|
export class VersionComponent implements AfterViewInit {
|
||||||
private readonly links: NavbarLink[];
|
private readonly links: NavbarLink[];
|
||||||
|
public cards: SimpleBootstrapCard[];
|
||||||
|
public currentVersion?: DaemonVersion;
|
||||||
|
public latestVersion?: DaemonVersion;
|
||||||
|
|
||||||
constructor(private navbarService: NavbarService, private daemonService: DaemonService) {
|
constructor(private navbarService: NavbarService, private daemonService: DaemonService) {
|
||||||
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')
|
||||||
];
|
];
|
||||||
|
this.cards = this.createCards();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit(): void {
|
private createCards(): SimpleBootstrapCard[] {
|
||||||
this.navbarService.setLinks(this.links);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<nav class="navbar navbar-expand-lg d-flex justify-content-center py-3 text-bg-dark">
|
<nav class="navbar navbar-expand-lg d-flex justify-content-center py-3 text-bg-dark">
|
||||||
|
|
||||||
<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">
|
<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>
|
<span class="fs-4"><strong>Monero Daemon</strong></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
<div class="d-flex flex-column flex-shrink-0 p-3 text-bg-dark" style="width: 280px;">
|
<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">
|
<ul class="nav nav-pills flex-column mb-auto">
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,8 @@ export class SidebarComponent implements OnChanges {
|
||||||
private createLightLinks(): NavLink[] {
|
private createLightLinks(): NavLink[] {
|
||||||
return [
|
return [
|
||||||
new NavLink('Dashboard', '/detail', 'bi bi-speedometer2'),
|
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')
|
new NavLink('Settings', '/settings', 'bi bi-gear')
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
11
src/app/shared/utils/SimpleBootstrapCard.ts
Normal file
11
src/app/shared/utils/SimpleBootstrapCard.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
1
src/app/shared/utils/index.ts
Normal file
1
src/app/shared/utils/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export { SimpleBootstrapCard } from "./SimpleBootstrapCard";
|
|
@ -1,13 +1,19 @@
|
||||||
export class DaemonVersion {
|
export class DaemonVersion {
|
||||||
public readonly version: number;
|
public readonly version: number;
|
||||||
public readonly release: boolean;
|
public readonly release: boolean;
|
||||||
|
public readonly fullname: string;
|
||||||
|
|
||||||
constructor(version: number, release: boolean) {
|
constructor(version: number, release: boolean, fullname: string = '') {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.release = release;
|
this.release = release;
|
||||||
|
this.fullname = fullname;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static parse(version: any) {
|
public static parse(version: any) {
|
||||||
|
if (typeof version == 'string') {
|
||||||
|
return new DaemonVersion(0, false, version);
|
||||||
|
}
|
||||||
|
|
||||||
const v: number = version.version;
|
const v: number = version.version;
|
||||||
const release: boolean = version.release;
|
const release: boolean = version.release;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,9 @@ html, body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: #373636;
|
background-color: #373636;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
min-height: 100vh;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
min-height: -webkit-fill-available;
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
|
|
Loading…
Reference in a new issue