mirror of
https://github.com/skot/ESP-Miner.git
synced 2025-11-19 10:31:21 +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">
|
||||||
<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"
|
<h2>Swarm</h2>
|
||||||
*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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container">
|
<ng-container *ngIf="swarm.length; else empty">
|
||||||
<table cellspacing="0" cellpadding="0" class="text-sm md:text-base">
|
<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="card mt-2 overflow-x-auto">
|
||||||
|
<table class="w-full">
|
||||||
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th *ngFor="let field of [
|
<th *ngFor="let field of [
|
||||||
{
|
{ label: 'Hostname', name: 'hostname' },
|
||||||
name: 'IP',
|
{ label: 'IP', name: 'IP' },
|
||||||
label: 'IP'
|
{ label: 'Hashrate', name: 'hashRate' },
|
||||||
},
|
{ label: 'Shares', name: 'sharesAccepted' },
|
||||||
{
|
{ label: 'Best Diff', name: 'bestDiff' },
|
||||||
name: 'hostname',
|
{ label: 'Uptime', name: 'uptimeSeconds' },
|
||||||
label: 'Hostname'
|
{ label: 'Power', name: 'power' },
|
||||||
},
|
{ label: 'Temp', name: 'temp' },
|
||||||
{
|
{ label: 'Pool Diff', name: 'poolDifficulty' },
|
||||||
name: 'hashRate',
|
{ label: 'Version', name: 'version' },
|
||||||
label: 'Hashrate'
|
]" 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>
|
||||||
name: 'sharesAccepted',
|
<i class="pi text-xs absolute right-0" [ngClass]="{
|
||||||
label: 'Shares'
|
'pi-sort-up-fill': sortField === field.name && sortDirection === 'asc',
|
||||||
},
|
'pi-sort-down-fill': sortField === field.name && sortDirection === 'desc'
|
||||||
{
|
|
||||||
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'
|
|
||||||
}"></i>
|
}"></i>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th>Edit</th>
|
<th></th>
|
||||||
<th>Restart</th>
|
|
||||||
<th>Remove</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
<ng-container *ngFor="let axe of swarm">
|
</thead>
|
||||||
<tr>
|
<tbody>
|
||||||
|
<tr *ngFor="let axe of swarm">
|
||||||
<td>
|
<td>
|
||||||
<a
|
<a [href]="'http://' + axe.IP" target="_blank" [ngClass]="'text-' + axe.swarmColor + '-500'">
|
||||||
[ngClass]="'text-'+axe.swarmColor+'-500'"
|
{{axe.hostname}}
|
||||||
[href]="'http://'+axe.IP"
|
</a>
|
||||||
target="_blank"
|
</td>
|
||||||
[pTooltip]="axe.deviceModel || 'Other'"
|
<td>
|
||||||
tooltipPosition="top">{{axe.IP}}</a>
|
<a [href]="'http://' + axe.IP" target="_blank" class="text-900">{{axe.IP}}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>{{axe.hostname}}</td>
|
|
||||||
<td>{{axe.hashRate * 1000000000 | hashSuffix}}</td>
|
<td>{{axe.hashRate * 1000000000 | hashSuffix}}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="w-min cursor-pointer"
|
<p class="cursor-pointer m-0" pTooltip="Shares Accepted" tooltipPosition="top">
|
||||||
pTooltip="Shares Accepted"
|
|
||||||
tooltipPosition="top">
|
|
||||||
{{axe.sharesAccepted | number: '1.0-0'}}
|
{{axe.sharesAccepted | number: '1.0-0'}}
|
||||||
</div>
|
</p>
|
||||||
<div class="text-sm w-min cursor-pointer"
|
<p class="text-500 cursor-pointer m-0" pTooltip="Shares Rejected" tooltipPosition="top">
|
||||||
pTooltip="Shares Rejected"
|
|
||||||
tooltipPosition="top">
|
|
||||||
{{axe.sharesRejected | number: '1.0-0'}}
|
{{axe.sharesRejected | number: '1.0-0'}}
|
||||||
</div>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="w-min cursor-pointer"
|
<p class="cursor-pointer m-0" pTooltip="Best Diff" tooltipPosition="top">
|
||||||
pTooltip="Best Diff"
|
|
||||||
tooltipPosition="top">
|
|
||||||
{{axe.bestDiff}}
|
{{axe.bestDiff}}
|
||||||
</div>
|
</p>
|
||||||
<div class="text-sm w-min cursor-pointer"
|
<p class="text-500 cursor-pointer m-0" pTooltip="Best Session Diff" tooltipPosition="top">
|
||||||
pTooltip="Best Session Diff"
|
|
||||||
tooltipPosition="top">
|
|
||||||
{{axe.bestSessionDiff}}
|
{{axe.bestSessionDiff}}
|
||||||
</div>
|
</p>
|
||||||
</td>
|
</td>
|
||||||
<td>{{axe.uptimeSeconds | dateAgo: {intervals: 2} }}</td>
|
<td class="cursor-pointer" pTooltip="{{axe.uptimeSeconds | dateAgo}}" tooltipPosition="top">
|
||||||
<td>{{axe.power | number: '1.1-1'}} <small>W</small> </td>
|
{{axe.uptimeSeconds | dateAgo: {intervals: 1} }}
|
||||||
|
</td>
|
||||||
|
<td>{{axe.power | number: '1.1-1'}} W</td>
|
||||||
<td>
|
<td>
|
||||||
<div
|
<p class="cursor-pointer m-0" [ngClass]="{'text-orange-500': axe.temp > 68}" pTooltip="ASIC Temperature" tooltipPosition="top">
|
||||||
[ngClass]="{'text-orange-500': axe.temp > 68}"
|
{{axe.temp | number: '1.0-1'}} °C
|
||||||
pTooltip="ASIC Temperature"
|
</p>
|
||||||
tooltipPosition="top">
|
<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.temp | number: '1.0-1'}} °<small>C</small>
|
{{axe.vrTemp | number: '1.0-1'}} °C
|
||||||
</div>
|
</p>
|
||||||
<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>
|
||||||
<td>{{axe.poolDifficulty}}</td>
|
<td>{{axe.poolDifficulty}}</td>
|
||||||
<td>{{axe.version}}</td>
|
<td>{{axe.version}}</td>
|
||||||
<td><p-button icon="pi pi-pencil" pp-button (click)="edit(axe)"></p-button></td>
|
<td>
|
||||||
<td><p-button icon="pi pi-sync" pp-button severity="danger" (click)="restart(axe)"></p-button></td>
|
<p-button
|
||||||
<td><p-button icon="pi pi-trash" pp-button severity="secondary" (click)="remove(axe)"></p-button></td>
|
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>
|
</tr>
|
||||||
</ng-container>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-2 mt-3 text-sm justify-content-center">
|
<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 [class]="'text-' + item.swarmColor + '-500'">●</span>
|
||||||
<span>{{ item.deviceModel || 'Other' }} ({{item.asicCount > 1 ? item.asicCount + 'x ' : ''}}{{item.ASICModel}})</span>
|
<span>{{ item.deviceModel || 'Other' }} ({{ item.asicCount > 1 ? item.asicCount + 'x ' : '' }}{{ item.ASICModel }})</span>
|
||||||
</div>
|
</div>
|
||||||
</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 *ngIf="form" [formGroup]="form" class="card flex flex-column md:flex-row gap-3 mt-5">
|
||||||
<form [formGroup]="form">
|
<button pButton (click)="scanNetwork()" [disabled]="scanning" class="white-space-nowrap w-full md:w-auto block text-center button-text">
|
||||||
<div class="field grid p-fluid mb-0">
|
{{scanning ? 'Scanning...' : 'Auto Scan'}}
|
||||||
<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>
|
|
||||||
</button>
|
</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>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<app-modal [headline]="selectedAxeOs?.IP">
|
<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-edit *ngIf="selectedAxeOs" [uri]="'http://' + selectedAxeOs.IP"></app-edit>
|
||||||
</app-modal>
|
</app-modal>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -1,75 +1,41 @@
|
|||||||
table {
|
table {
|
||||||
width: 100%;
|
border-collapse: collapse;
|
||||||
border: 1px solid #304562;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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,
|
th,
|
||||||
td {
|
td {
|
||||||
border-bottom: 1px solid #304562;
|
padding: 0 0.5rem 0.5rem;
|
||||||
}
|
|
||||||
|
|
||||||
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%;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
th, td {
|
|
||||||
padding: 0.5rem;
|
|
||||||
text-align: left;
|
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 {
|
.button-text {
|
||||||
display: grid;
|
min-width: 8rem;
|
||||||
place-items: center;
|
}
|
||||||
|
::ng-deep .p-button.button-icon {
|
||||||
> span:first-child {
|
width: 2.2rem;
|
||||||
visibility: hidden;
|
height: 2.2rem;
|
||||||
grid-area: 1 / 1;
|
margin-top: 0.25rem;
|
||||||
}
|
|
||||||
|
|
||||||
> span:last-child {
|
|
||||||
grid-area: 1 / 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.refreshIntervalRef = window.setInterval(() => {
|
this.refreshIntervalRef = window.setInterval(() => {
|
||||||
if (!this.scanning && !this.isRefreshing) {
|
if (!this.scanning && !this.isRefreshing && this.swarm.length) {
|
||||||
this.refreshIntervalTime--;
|
this.refreshIntervalTime--;
|
||||||
if (this.refreshIntervalTime <= 0) {
|
if (this.refreshIntervalTime <= 0) {
|
||||||
this.refreshList(false);
|
this.refreshList(false);
|
||||||
@@ -132,6 +132,7 @@ export class SwarmComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
complete: () => {
|
complete: () => {
|
||||||
this.scanning = false;
|
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');
|
.reduce((max, curr) => this.compareBestDiff(max, curr), '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
get getFamilies(): SwarmDevice[] {
|
get deviceFamilies(): SwarmDevice[] {
|
||||||
return this.swarm.filter((v, i, a) =>
|
return this.swarm.filter((v, i, a) =>
|
||||||
a.findIndex(({ deviceModel, ASICModel, asicCount }) =>
|
a.findIndex(({ deviceModel, ASICModel, asicCount }) =>
|
||||||
v.deviceModel === deviceModel &&
|
v.deviceModel === deviceModel &&
|
||||||
|
|||||||
Reference in New Issue
Block a user