mirror of
https://github.com/skot/ESP-Miner.git
synced 2025-03-17 21:32:52 +01:00
Swarm styles, refresh on load, more combined stats, more info in table (#491)
* fix: swarm add less white space * fix: On mount refresh swarm data * fix: Allow setable swarm refresh time * fix: Increase swarm warning temp * fix: Add total power and best diff of swarm * fix: Add VR temp to swarm * fix: Increase extra swarm data font size * add rejected shares too on swarm page
This commit is contained in:
parent
c745cbb0e5
commit
5a385c20f0
@ -24,6 +24,7 @@ import { DateAgoPipe } from './pipes/date-ago.pipe';
|
||||
import { HashSuffixPipe } from './pipes/hash-suffix.pipe';
|
||||
import { PrimeNGModule } from './prime-ng.module';
|
||||
import { MessageModule } from 'primeng/message';
|
||||
import { TooltipModule } from 'primeng/tooltip';
|
||||
|
||||
const components = [
|
||||
AppComponent,
|
||||
@ -59,7 +60,8 @@ const components = [
|
||||
CommonModule,
|
||||
PrimeNGModule,
|
||||
AppLayoutModule,
|
||||
MessageModule
|
||||
MessageModule,
|
||||
TooltipModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: LocationStrategy, useClass: HashLocationStrategy },
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="card">
|
||||
<div class="card p-3">
|
||||
|
||||
<form [formGroup]="form">
|
||||
<div class="field grid p-fluid">
|
||||
<div class="field grid p-fluid mb-0">
|
||||
<label htmlFor="ip" class="col-12 mb-2 md:col-4 md:mb-0">Manual Addition</label>
|
||||
<div class="col-12 md:col-8">
|
||||
<p-inputGroup>
|
||||
@ -14,15 +14,35 @@
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<div class="flex gap-3 justify-content-between">
|
||||
<div class="flex flex-column sm:flex-row gap-4 justify-content-between">
|
||||
<div class="flex gap-1 sm:gap-3 text-sm md:text-base">
|
||||
<button pButton (click)="scanNetwork()" [disabled]="scanning">{{scanning ? 'Scanning...' : 'Automatic Scan'}}</button>
|
||||
<button pButton severity="secondary" (click)="refreshList()" [disabled]="scanning || isRefreshing">
|
||||
{{isRefreshing ? 'Refreshing...' : 'Refresh List (' + refreshIntervalTime + ')'}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="text-sm md:text-base">
|
||||
Total Hash Rate: <span class="text-primary">{{totalHashRate * 1000000000 | hashSuffix}}</span>
|
||||
<div class="flex align-items-center gap-2">
|
||||
<label for="refresh-interval" class="text-sm md:text-base">Refresh Interval:</label>
|
||||
<p-slider id="refresh-interval" class="pl-2 pr-2"
|
||||
[min]="5"
|
||||
[max]="30"
|
||||
[(ngModel)]="refreshTimeSet"
|
||||
[style]="{'width': '150px'}"
|
||||
[formControl]="refreshIntervalControl">
|
||||
</p-slider>
|
||||
<span class="text-sm md:text-base">{{refreshTimeSet}}s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-column sm:flex-row w-full gap-2 xl:gap-4 mt-4 mb-4">
|
||||
<div class="card mb-0 w-full text-center p-4">
|
||||
Total Hash Rate: <span class="text-primary">{{totals.hashRate * 1000000000 | hashSuffix}}</span>
|
||||
</div>
|
||||
<div class="card mb-0 w-full text-center p-4">
|
||||
Total Power: <span class="text-primary">{{totals.power | number: '1.1-1'}} <small>W</small></span>
|
||||
</div>
|
||||
<div class="card mb-0 w-full text-center p-4">
|
||||
Best Diff: <span class="text-primary">{{totals.bestDiff}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -32,7 +52,7 @@
|
||||
<th>IP</th>
|
||||
<th>Hash Rate</th>
|
||||
<th>Uptime</th>
|
||||
<th>Accepted</th>
|
||||
<th>Shares</th>
|
||||
<th>Power</th>
|
||||
<th>Temp</th>
|
||||
<th>Best Diff</th>
|
||||
@ -45,14 +65,43 @@
|
||||
<tr>
|
||||
<td>
|
||||
<a class="text-primary" [href]="'http://'+axe.IP" target="_blank">{{axe.IP}}</a>
|
||||
<div class="text-xs">{{axe.hostname}}</div>
|
||||
<div class="text-sm">{{axe.hostname}}</div>
|
||||
</td>
|
||||
<td>{{axe.hashRate * 1000000000 | hashSuffix}}</td>
|
||||
<td>{{axe.uptimeSeconds | dateAgo}}</td>
|
||||
<td>{{axe.sharesAccepted | number: '1.0-0'}}</td>
|
||||
<td>
|
||||
<div class="w-min cursor-pointer"
|
||||
pTooltip="Shares Accepted"
|
||||
tooltipPosition="top">
|
||||
{{axe.sharesAccepted | number: '1.0-0'}}
|
||||
</div>
|
||||
<div class="text-sm w-min cursor-pointer"
|
||||
pTooltip="Shares Rejected"
|
||||
tooltipPosition="top">
|
||||
{{axe.sharesRejected | number: '1.0-0'}}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{axe.power | number: '1.1-1'}} <small>W</small> </td>
|
||||
<td [ngClass]="{'text-orange-500': axe.temp > 65}">{{axe.temp | number: '1.0-1'}}°<small>C</small></td>
|
||||
<td>{{axe.bestDiff}}</td>
|
||||
<td>
|
||||
<div [ngClass]="{'text-orange-500': axe.temp > 68}">
|
||||
{{axe.temp | number: '1.0-1'}}°<small>C</small>
|
||||
</div>
|
||||
<div class="text-sm w-min cursor-pointer"
|
||||
[ngClass]="{'text-orange-500': axe.vrTemp > 90}"
|
||||
*ngIf="axe.vrTemp"
|
||||
pTooltip="Voltage Regulator Temperature"
|
||||
tooltipPosition="top">
|
||||
{{axe.vrTemp | number: '1.0-1'}}°<small>C</small>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{axe.bestDiff}}</div>
|
||||
<div class="text-sm w-min cursor-pointer"
|
||||
pTooltip="Best Session Diff"
|
||||
tooltipPosition="top">
|
||||
{{axe.bestSessionDiff}}
|
||||
</div>
|
||||
</td>
|
||||
<td>{{axe.version}}</td>
|
||||
<td><p-button icon="pi pi-pencil" pp-button (click)="edit(axe)"></p-button></td>
|
||||
<td><p-button icon="pi pi-sync" pp-button severity="danger" (click)="restart(axe)"></p-button></td>
|
||||
|
@ -1,11 +1,5 @@
|
||||
form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
border: 1px solid #304562;
|
||||
}
|
||||
|
||||
@ -57,7 +51,7 @@ a {
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin: 1rem 0;
|
||||
margin: 0;
|
||||
|
||||
table {
|
||||
min-width: 100%;
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
|
||||
import { ToastrService } from 'ngx-toastr';
|
||||
import { catchError, from, map, mergeMap, of, take, timeout, toArray } from 'rxjs';
|
||||
import { LocalStorageService } from 'src/app/local-storage.service';
|
||||
import { SystemService } from 'src/app/services/system.service';
|
||||
const REFRESH_TIME_SECONDS = 30;
|
||||
const SWARM_DATA = 'SWARM_DATA'
|
||||
const SWARM_REFRESH_TIME = 'SWARM_REFRESH_TIME';
|
||||
@Component({
|
||||
selector: 'app-swarm',
|
||||
templateUrl: './swarm.component.html',
|
||||
@ -24,12 +24,15 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
public scanning = false;
|
||||
|
||||
public refreshIntervalRef!: number;
|
||||
public refreshIntervalTime = REFRESH_TIME_SECONDS;
|
||||
public refreshIntervalTime = 30;
|
||||
public refreshTimeSet = 30;
|
||||
|
||||
public totalHashRate: number = 0;
|
||||
public totals: { hashRate: number, power: number, bestDiff: string } = { hashRate: 0, power: 0, bestDiff: '0' };
|
||||
|
||||
public isRefreshing = false;
|
||||
|
||||
public refreshIntervalControl: FormControl;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private systemService: SystemService,
|
||||
@ -40,7 +43,18 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.form = this.fb.group({
|
||||
manualAddIp: [null, [Validators.required, Validators.pattern('(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)')]]
|
||||
})
|
||||
});
|
||||
|
||||
const storedRefreshTime = this.localStorageService.getNumber(SWARM_REFRESH_TIME) ?? 30;
|
||||
this.refreshIntervalTime = storedRefreshTime;
|
||||
this.refreshTimeSet = storedRefreshTime;
|
||||
this.refreshIntervalControl = new FormControl(storedRefreshTime);
|
||||
|
||||
this.refreshIntervalControl.valueChanges.subscribe(value => {
|
||||
this.refreshIntervalTime = value;
|
||||
this.refreshTimeSet = value;
|
||||
this.localStorageService.setNumber(SWARM_REFRESH_TIME, value);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -52,7 +66,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
//this.swarm$ = this.scanNetwork('192.168.1.23', '255.255.255.0').pipe(take(1));
|
||||
} else {
|
||||
this.swarm = swarmData;
|
||||
this.calculateTotalHashRate();
|
||||
this.refreshList();
|
||||
}
|
||||
|
||||
this.refreshIntervalRef = window.setInterval(() => {
|
||||
@ -118,7 +132,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
const newItems = result.filter(item => !existingIps.has(item.IP));
|
||||
this.swarm = [...this.swarm, ...newItems].sort(this.sortByIp.bind(this));
|
||||
this.localStorageService.setObject(SWARM_DATA, this.swarm);
|
||||
this.calculateTotalHashRate();
|
||||
this.calculateTotals();
|
||||
},
|
||||
complete: () => {
|
||||
this.scanning = false;
|
||||
@ -140,7 +154,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
this.swarm.push({ IP: newIp, ...res });
|
||||
this.swarm = this.swarm.sort(this.sortByIp.bind(this));
|
||||
this.localStorageService.setObject(SWARM_DATA, this.swarm);
|
||||
this.calculateTotalHashRate();
|
||||
this.calculateTotals();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -166,11 +180,11 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
public remove(axeOs: any) {
|
||||
this.swarm = this.swarm.filter(axe => axe.IP != axeOs.IP);
|
||||
this.localStorageService.setObject(SWARM_DATA, this.swarm);
|
||||
this.calculateTotalHashRate();
|
||||
this.calculateTotals();
|
||||
}
|
||||
|
||||
public refreshList() {
|
||||
this.refreshIntervalTime = REFRESH_TIME_SECONDS;
|
||||
this.refreshIntervalTime = this.refreshTimeSet;
|
||||
const ips = this.swarm.map(axeOs => axeOs.IP);
|
||||
this.isRefreshing = true;
|
||||
|
||||
@ -209,7 +223,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
next: (result) => {
|
||||
this.swarm = result.sort(this.sortByIp.bind(this));
|
||||
this.localStorageService.setObject(SWARM_DATA, this.swarm);
|
||||
this.calculateTotalHashRate();
|
||||
this.calculateTotals();
|
||||
this.isRefreshing = false;
|
||||
},
|
||||
complete: () => {
|
||||
@ -222,8 +236,32 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
return this.ipToInt(a.IP) - this.ipToInt(b.IP);
|
||||
}
|
||||
|
||||
private calculateTotalHashRate() {
|
||||
this.totalHashRate = this.swarm.reduce((sum, axe) => sum + (axe.hashRate || 0), 0);
|
||||
private convertBestDiffToNumber(bestDiff: string): number {
|
||||
if (!bestDiff) return 0;
|
||||
const value = parseFloat(bestDiff);
|
||||
const unit = bestDiff.slice(-1).toUpperCase();
|
||||
switch (unit) {
|
||||
case 'T': return value * 1000000000000;
|
||||
case 'G': return value * 1000000000;
|
||||
case 'M': return value * 1000000;
|
||||
case 'K': return value * 1000;
|
||||
default: return value;
|
||||
}
|
||||
}
|
||||
|
||||
private formatBestDiff(value: number): string {
|
||||
if (value >= 1000000000000) return `${(value / 1000000000000).toFixed(2)}T`;
|
||||
if (value >= 1000000000) return `${(value / 1000000000).toFixed(2)}G`;
|
||||
if (value >= 1000000) return `${(value / 1000000).toFixed(2)}M`;
|
||||
if (value >= 1000) return `${(value / 1000).toFixed(2)}K`;
|
||||
return value.toFixed(2);
|
||||
}
|
||||
|
||||
private calculateTotals() {
|
||||
this.totals.hashRate = this.swarm.reduce((sum, axe) => sum + (axe.hashRate || 0), 0);
|
||||
this.totals.power = this.swarm.reduce((sum, axe) => sum + (axe.power || 0), 0);
|
||||
const maxDiff = Math.max(...this.swarm.map(axe => this.convertBestDiffToNumber(axe.bestDiff)));
|
||||
this.totals.bestDiff = this.formatBestDiff(maxDiff);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -34,4 +34,13 @@ export class LocalStorageService {
|
||||
}
|
||||
return JSON.parse(item);
|
||||
}
|
||||
|
||||
setNumber(key: string, value: number) {
|
||||
localStorage.setItem(key, value.toString());
|
||||
}
|
||||
|
||||
getNumber(key: string): number | null {
|
||||
const value = localStorage.getItem(key);
|
||||
return value ? Number(value) : null;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user