mirror of
https://github.com/skot/ESP-Miner.git
synced 2025-11-19 18:37:20 +01:00
Swarm Facelift (#1231)
* Action bar * Add swarm table * Add vrTemp check * Reorder hostname & IP * Optimize the refresh button to prevent flickering * Prevent column width jumps on sorting --------- Co-authored-by: duckaxe <>
This commit is contained in:
@@ -1,192 +1,154 @@
|
||||
<div class="flex flex-column sm:flex-row w-full gap-2 xl:gap-4 mb-4">
|
||||
<div class="card mb-0 w-full text-center p-4 min-w-max flex sm:flex-column xl:flex-row justify-content-center gap-1"
|
||||
*ngFor="let metric of [
|
||||
{
|
||||
label: 'Total Devices',
|
||||
value: swarm.length
|
||||
},
|
||||
{
|
||||
label: 'Total Hashrate',
|
||||
value: totals.hashRate * 1000000000 | hashSuffix
|
||||
},
|
||||
{
|
||||
label: 'Total Power',
|
||||
value: (totals.power | number: '1.1-1') + ' W'
|
||||
},
|
||||
{
|
||||
label: 'Best Diff',
|
||||
value: totals.bestDiff
|
||||
}
|
||||
]"
|
||||
>
|
||||
{{metric.label}}: <span class="text-primary">{{metric.value}}</span>
|
||||
<div class="card">
|
||||
<h2>Swarm</h2>
|
||||
|
||||
<ng-container *ngIf="swarm.length; else empty">
|
||||
<div class="grid">
|
||||
<div class="col-6 xl:col-3" *ngFor="let metric of [
|
||||
{ label: 'Total Devices', value: swarm.length },
|
||||
{ label: 'Total Hashrate', value: totals.hashRate * 1000000000 | hashSuffix},
|
||||
{ label: 'Total Power', value: (totals.power | number: '1.1-1') + ' W' },
|
||||
{ label: 'Best Diff', value: totals.bestDiff }
|
||||
]">
|
||||
<div class="card flex flex-column mb-0">
|
||||
<span class="text-500 font-medium mb-3">{{metric.label}}</span>
|
||||
<div class="text-900 font-medium text-2xl">{{metric.value}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table cellspacing="0" cellpadding="0" class="text-sm md:text-base">
|
||||
<div class="card mt-2 overflow-x-auto">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th *ngFor="let field of [
|
||||
{
|
||||
name: 'IP',
|
||||
label: 'IP'
|
||||
},
|
||||
{
|
||||
name: 'hostname',
|
||||
label: 'Hostname'
|
||||
},
|
||||
{
|
||||
name: 'hashRate',
|
||||
label: 'Hashrate'
|
||||
},
|
||||
{
|
||||
name: 'sharesAccepted',
|
||||
label: 'Shares'
|
||||
},
|
||||
{
|
||||
name: 'bestDiff',
|
||||
label: 'Best Diff'
|
||||
},
|
||||
{
|
||||
name: 'uptimeSeconds',
|
||||
label: 'Uptime'
|
||||
},
|
||||
{
|
||||
name: 'power',
|
||||
label: 'Power'
|
||||
},
|
||||
{
|
||||
name: 'temp',
|
||||
label: 'Temp'
|
||||
},
|
||||
{
|
||||
name: 'poolDifficulty',
|
||||
label: 'Pool Diff'
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
label: 'Version'
|
||||
},
|
||||
]">
|
||||
<div class="flex align-items-center cursor-pointer select-none h-full" (click)="sortBy(field.name)">
|
||||
<span class="flex-1">{{field.label}}</span>
|
||||
<i class="pi text-xs flex items-center" [ngClass]="{
|
||||
'pi-sort-alt': sortField !== field.name,
|
||||
'pi-sort-amount-up-alt': sortField === field.name && sortDirection === 'asc',
|
||||
'pi-sort-amount-down': sortField === field.name && sortDirection === 'desc'
|
||||
{ label: 'Hostname', name: 'hostname' },
|
||||
{ label: 'IP', name: 'IP' },
|
||||
{ label: 'Hashrate', name: 'hashRate' },
|
||||
{ label: 'Shares', name: 'sharesAccepted' },
|
||||
{ label: 'Best Diff', name: 'bestDiff' },
|
||||
{ label: 'Uptime', name: 'uptimeSeconds' },
|
||||
{ label: 'Power', name: 'power' },
|
||||
{ label: 'Temp', name: 'temp' },
|
||||
{ label: 'Pool Diff', name: 'poolDifficulty' },
|
||||
{ label: 'Version', name: 'version' },
|
||||
]" class="text-500 font-medium">
|
||||
<div class="flex align-items-center cursor-pointer select-none relative" (click)="sortBy(field.name)">
|
||||
<span class="pr-3">{{field.label}}</span>
|
||||
<i class="pi text-xs absolute right-0" [ngClass]="{
|
||||
'pi-sort-up-fill': sortField === field.name && sortDirection === 'asc',
|
||||
'pi-sort-down-fill': sortField === field.name && sortDirection === 'desc'
|
||||
}"></i>
|
||||
</div>
|
||||
</th>
|
||||
<th>Edit</th>
|
||||
<th>Restart</th>
|
||||
<th>Remove</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<ng-container *ngFor="let axe of swarm">
|
||||
<tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let axe of swarm">
|
||||
<td>
|
||||
<a
|
||||
[ngClass]="'text-'+axe.swarmColor+'-500'"
|
||||
[href]="'http://'+axe.IP"
|
||||
target="_blank"
|
||||
[pTooltip]="axe.deviceModel || 'Other'"
|
||||
tooltipPosition="top">{{axe.IP}}</a>
|
||||
<a [href]="'http://' + axe.IP" target="_blank" [ngClass]="'text-' + axe.swarmColor + '-500'">
|
||||
{{axe.hostname}}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a [href]="'http://' + axe.IP" target="_blank" class="text-900">{{axe.IP}}</a>
|
||||
</td>
|
||||
<td>{{axe.hostname}}</td>
|
||||
<td>{{axe.hashRate * 1000000000 | hashSuffix}}</td>
|
||||
<td>
|
||||
<div class="w-min cursor-pointer"
|
||||
pTooltip="Shares Accepted"
|
||||
tooltipPosition="top">
|
||||
<p class="cursor-pointer m-0" 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">
|
||||
</p>
|
||||
<p class="text-500 cursor-pointer m-0" pTooltip="Shares Rejected" tooltipPosition="top">
|
||||
{{axe.sharesRejected | number: '1.0-0'}}
|
||||
</div>
|
||||
</p>
|
||||
</td>
|
||||
<td>
|
||||
<div class="w-min cursor-pointer"
|
||||
pTooltip="Best Diff"
|
||||
tooltipPosition="top">
|
||||
<p class="cursor-pointer m-0" pTooltip="Best Diff" tooltipPosition="top">
|
||||
{{axe.bestDiff}}
|
||||
</div>
|
||||
<div class="text-sm w-min cursor-pointer"
|
||||
pTooltip="Best Session Diff"
|
||||
tooltipPosition="top">
|
||||
</p>
|
||||
<p class="text-500 cursor-pointer m-0" pTooltip="Best Session Diff" tooltipPosition="top">
|
||||
{{axe.bestSessionDiff}}
|
||||
</div>
|
||||
</p>
|
||||
</td>
|
||||
<td>{{axe.uptimeSeconds | dateAgo: {intervals: 2} }}</td>
|
||||
<td>{{axe.power | number: '1.1-1'}} <small>W</small> </td>
|
||||
<td class="cursor-pointer" pTooltip="{{axe.uptimeSeconds | dateAgo}}" tooltipPosition="top">
|
||||
{{axe.uptimeSeconds | dateAgo: {intervals: 1} }}
|
||||
</td>
|
||||
<td>{{axe.power | number: '1.1-1'}} W</td>
|
||||
<td>
|
||||
<div
|
||||
[ngClass]="{'text-orange-500': axe.temp > 68}"
|
||||
pTooltip="ASIC Temperature"
|
||||
tooltipPosition="top">
|
||||
{{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>
|
||||
<p class="cursor-pointer m-0" [ngClass]="{'text-orange-500': axe.temp > 68}" pTooltip="ASIC Temperature" tooltipPosition="top">
|
||||
{{axe.temp | number: '1.0-1'}} °C
|
||||
</p>
|
||||
<p class="text-500 cursor-pointer m-0" *ngIf="axe.vrTemp" [ngClass]="{'text-orange-500': axe.vrTemp > 90}" pTooltip="Voltage Regulator Temperature" tooltipPosition="top">
|
||||
{{axe.vrTemp | number: '1.0-1'}} °C
|
||||
</p>
|
||||
</td>
|
||||
<td>{{axe.poolDifficulty}}</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>
|
||||
<td><p-button icon="pi pi-trash" pp-button severity="secondary" (click)="remove(axe)"></p-button></td>
|
||||
<td>
|
||||
<p-button
|
||||
icon="pi pi-pencil flex align-items-center justify-content-center"
|
||||
pp-button
|
||||
severity="secondary"
|
||||
styleClass="button-icon"
|
||||
(click)="edit(axe)" />
|
||||
<p-button
|
||||
icon="pi pi-refresh flex align-items-center justify-content-center"
|
||||
pp-button
|
||||
severity="secondary"
|
||||
styleClass="button-icon"
|
||||
class="mx-2"
|
||||
(click)="restart(axe)" />
|
||||
<p-button
|
||||
icon="pi pi-trash flex align-items-center justify-content-center"
|
||||
pp-button
|
||||
severity="secondary"
|
||||
styleClass="button-icon"
|
||||
(click)="remove(axe)" />
|
||||
</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 mt-3 text-sm justify-content-center">
|
||||
<div *ngFor="let item of getFamilies" class="flex align-items-center gap-1">
|
||||
<div *ngFor="let item of deviceFamilies" class="flex align-items-center gap-1">
|
||||
<span [class]="'text-' + item.swarmColor + '-500'">●</span>
|
||||
<span>{{ item.deviceModel || 'Other' }} ({{ item.asicCount > 1 ? item.asicCount + 'x ' : '' }}{{ item.ASICModel }})</span>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
</div>
|
||||
<ng-template #empty>
|
||||
<p class="text-500 text-center pt-5">
|
||||
Scan your network for devices or add a device manually by using its IP address.
|
||||
</p>
|
||||
</ng-template>
|
||||
|
||||
<div class="card p-3 mt-4">
|
||||
<form [formGroup]="form">
|
||||
<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>
|
||||
<input pInputText id="manualAddIp" formControlName="manualAddIp" type="text" />
|
||||
<button pButton [disabled]="form.invalid" (click)="add()">Add</button>
|
||||
</p-inputGroup>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<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" class="refresh-button">
|
||||
<span aria-hidden="true">Refresh List (30)</span>
|
||||
<span>{{ isRefreshing ? 'Refreshing...' : 'Refresh List (' + refreshIntervalTime + ')' }}</span>
|
||||
<form *ngIf="form" [formGroup]="form" class="card flex flex-column md:flex-row gap-3 mt-5">
|
||||
<button pButton (click)="scanNetwork()" [disabled]="scanning" class="white-space-nowrap w-full md:w-auto block text-center button-text">
|
||||
{{scanning ? 'Scanning...' : 'Auto Scan'}}
|
||||
</button>
|
||||
</div>
|
||||
<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"
|
||||
[style]="{'width': '150px'}"
|
||||
[formControl]="refreshIntervalControl">
|
||||
</p-slider>
|
||||
<span class="text-sm md:text-base">{{refreshTimeSet}} s</span>
|
||||
|
||||
<div class="flex align-items-center gap-3" *ngIf="this.swarm.length">
|
||||
<button pButton (click)="refreshList()" class="flex justify-content-between button-text">
|
||||
<span>{{isRefreshing ? 'Refreshing...' : 'Refresh'}}</span>
|
||||
<span *ngIf="!isRefreshing" class="text-sm text-700">{{refreshIntervalTime}}</span>
|
||||
</button>
|
||||
<div class="flex flex-grow-1 align-items-center gap-3">
|
||||
<p-slider class="w-full md:w-3rem xl:w-6rem" [min]="5" [max]="30" [formControl]="refreshIntervalControl"></p-slider>
|
||||
<span class="text-sm white-space-nowrap">{{refreshTimeSet}} s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="md:ml-auto">
|
||||
<p-inputGroup>
|
||||
<input pInputText placeholder="Add Device by IP" formControlName="manualAddIp" type="text" class="xl:w-20rem" />
|
||||
<button pButton [disabled]="form.invalid" (click)="add()" class="ml-1">Add</button>
|
||||
</p-inputGroup>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<app-modal [headline]="selectedAxeOs?.IP">
|
||||
<app-edit *ngIf="selectedAxeOs" [uri]="'http://' + selectedAxeOs.IP"></app-edit>
|
||||
</app-modal>
|
||||
</div>
|
||||
|
||||
@@ -1,75 +1,41 @@
|
||||
table {
|
||||
width: 100%;
|
||||
border: 1px solid #304562;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
background-color: #1f2d40;
|
||||
padding: 0;
|
||||
|
||||
> div {
|
||||
height: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.pi {
|
||||
opacity: 0.7;
|
||||
height: 1em;
|
||||
line-height: 1;
|
||||
|
||||
&.pi-sort-amount-up-alt,
|
||||
&.pi-sort-amount-down {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
border-bottom: 1px solid #304562;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
th > div {
|
||||
min-width: 100px;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
margin: 0;
|
||||
|
||||
table {
|
||||
min-width: 100%;
|
||||
padding: 0 0.5rem 0.5rem;
|
||||
white-space: nowrap;
|
||||
|
||||
th, td {
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
td {
|
||||
padding-top: 0.5rem;
|
||||
|
||||
p {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
tbody tr {
|
||||
border-top: 1px solid var(--surface-border);
|
||||
|
||||
&:last-child td {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.refresh-button {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
|
||||
> span:first-child {
|
||||
visibility: hidden;
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
|
||||
> span:last-child {
|
||||
grid-area: 1 / 1;
|
||||
.button-text {
|
||||
min-width: 8rem;
|
||||
}
|
||||
::ng-deep .p-button.button-icon {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
this.refreshIntervalRef = window.setInterval(() => {
|
||||
if (!this.scanning && !this.isRefreshing) {
|
||||
if (!this.scanning && !this.isRefreshing && this.swarm.length) {
|
||||
this.refreshIntervalTime--;
|
||||
if (this.refreshIntervalTime <= 0) {
|
||||
this.refreshList(false);
|
||||
@@ -132,6 +132,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
complete: () => {
|
||||
this.scanning = false;
|
||||
this.refreshIntervalTime = this.refreshTimeSet;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -316,7 +317,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
||||
.reduce((max, curr) => this.compareBestDiff(max, curr), '0');
|
||||
}
|
||||
|
||||
get getFamilies(): SwarmDevice[] {
|
||||
get deviceFamilies(): SwarmDevice[] {
|
||||
return this.swarm.filter((v, i, a) =>
|
||||
a.findIndex(({ deviceModel, ASICModel, asicCount }) =>
|
||||
v.deviceModel === deviceModel &&
|
||||
|
||||
Reference in New Issue
Block a user