mirror of
https://github.com/benjamin-wilson/public-pool-ui.git
synced 2025-03-27 02:01:42 +01:00
misc
This commit is contained in:
parent
48516e468e
commit
3ba7ef8ca9
@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { DashboardComponent } from './components/dashboard/dashboard.component';
|
||||
import { SplashComponent } from './components/splash/splash.component';
|
||||
import { WorkerGroupComponent } from './components/worker-group/worker-group.component';
|
||||
import { WorkerComponent } from './components/worker/worker.component';
|
||||
import { AppLayoutComponent } from './layout/app.layout.component';
|
||||
|
||||
@ -23,9 +24,19 @@ const routes: Routes = [
|
||||
component: DashboardComponent,
|
||||
},
|
||||
{
|
||||
path: ':workerId',
|
||||
component: WorkerComponent
|
||||
path: ':workerName',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
component: WorkerGroupComponent
|
||||
},
|
||||
{
|
||||
path: ':workerId',
|
||||
component: WorkerComponent
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -13,6 +13,8 @@ import { SplashComponent } from './components/splash/splash.component';
|
||||
import { WorkerComponent } from './components/worker/worker.component';
|
||||
import { AppLayoutModule } from './layout/app.layout.module';
|
||||
import { NumberSuffixPipe } from './pipes/number-suffix.pipe';
|
||||
import { DateAgoPipe } from './pipes/date-ago.pipe';
|
||||
import { WorkerGroupComponent } from './components/worker-group/worker-group.component';
|
||||
|
||||
|
||||
|
||||
@ -22,7 +24,9 @@ import { NumberSuffixPipe } from './pipes/number-suffix.pipe';
|
||||
SplashComponent,
|
||||
DashboardComponent,
|
||||
WorkerComponent,
|
||||
NumberSuffixPipe
|
||||
NumberSuffixPipe,
|
||||
DateAgoPipe,
|
||||
WorkerGroupComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
@ -2,23 +2,58 @@
|
||||
<div class="card">
|
||||
|
||||
|
||||
<p-table [rowHover]="true" [value]="clientInfo.workers" [tableStyle]="{ 'min-width': '50rem' }">
|
||||
<p-table [rowHover]="true" groupRowsBy="name" dataKey="name" rowGroupMode="subheader"
|
||||
[value]="clientInfo.workers" [tableStyle]="{ 'min-width': '50rem' }">
|
||||
|
||||
|
||||
<ng-template pTemplate="header">
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>ID</th>
|
||||
<th>Session ID</th>
|
||||
<th>Hash Rate</th>
|
||||
<th>Best Luck</th>
|
||||
<th>Best Difficulty</th>
|
||||
<th>Uptime</th>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<ng-template pTemplate="body" let-worker>
|
||||
<tr [routerLink]="[worker.id]">
|
||||
<td>{{worker.name}}</td>
|
||||
<td>{{worker.id}}</td>
|
||||
<td>{{worker.hashRate}} Gh/s</td>
|
||||
|
||||
|
||||
<ng-template pTemplate="groupheader" let-worker let-rowIndex="rowIndex" let-expanded="expanded">
|
||||
<tr [routerLink]="[worker.name]">
|
||||
<td>
|
||||
<button (click)="$event.stopImmediatePropagation()" type="button" pButton pRipple
|
||||
[pRowToggler]="worker" class="p-button-text p-button-rounded p-button-plain mr-2"
|
||||
[icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"></button>
|
||||
|
||||
<span class="worker-name font-bold ml-2">{{worker.name}} </span>
|
||||
|
||||
</td>
|
||||
<td>
|
||||
{{getSessionCount(worker.name, clientInfo.workers)}} Sessions
|
||||
</td>
|
||||
<td>
|
||||
{{getTotalHashRate(worker.name, clientInfo.workers)}} GH/s
|
||||
</td>
|
||||
<td>
|
||||
{{getBestDifficulty(worker.name, clientInfo.workers) | numberSuffix}}
|
||||
</td>
|
||||
<td>
|
||||
{{getTotalUptime(worker.name, clientInfo.workers) | dateAgo}} (cumulative)
|
||||
</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
|
||||
<ng-template pTemplate="rowexpansion" let-worker>
|
||||
<tr [routerLink]="[worker.name, worker.sessionId]">
|
||||
<td></td>
|
||||
<td>{{worker.sessionId}}</td>
|
||||
<td>{{worker.hashRate}} GH/s</td>
|
||||
<td>{{worker.bestDifficulty | numberSuffix}}</td>
|
||||
<td>{{worker.startTime | dateAgo}}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
|
||||
|
||||
</p-table>
|
||||
|
||||
|
||||
|
@ -0,0 +1,3 @@
|
||||
.worker-name {
|
||||
line-height: 42px;
|
||||
}
|
@ -20,4 +20,39 @@ export class DashboardComponent {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public getSessionCount(name: string, workers: any[]) {
|
||||
const workersByName = workers.filter(w => w.name == name);
|
||||
return workersByName.length;
|
||||
}
|
||||
|
||||
public getTotalHashRate(name: string, workers: any[]) {
|
||||
const workersByName = workers.filter(w => w.name == name);
|
||||
const sum = workersByName.reduce((pre, cur, idx, arr) => {
|
||||
return pre += cur.hashRate;
|
||||
}, 0);
|
||||
return Math.floor(sum);
|
||||
}
|
||||
|
||||
public getBestDifficulty(name: string, workers: any[]) {
|
||||
const workersByName = workers.filter(w => w.name == name);
|
||||
const best = workersByName.reduce((pre, cur, idx, arr) => {
|
||||
if (cur.bestDifficulty > pre) {
|
||||
return cur.bestDifficulty;
|
||||
}
|
||||
return pre;
|
||||
}, 0);
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
public getTotalUptime(name: string, workers: any[]) {
|
||||
const now = new Date().getTime();
|
||||
const workersByName = workers.filter(w => w.name == name);
|
||||
const sum = workersByName.reduce((pre, cur, idx, arr) => {
|
||||
return pre += now - new Date(cur.startTime).getTime();
|
||||
}, 0);
|
||||
return new Date(now - sum);
|
||||
}
|
||||
}
|
||||
|
75
src/app/components/worker-group/worker-group.component.html
Normal file
75
src/app/components/worker-group/worker-group.component.html
Normal file
@ -0,0 +1,75 @@
|
||||
<div class="grid">
|
||||
<ng-container *ngIf="workerInfo$ | async as workerInfo">
|
||||
<div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">ID</span>
|
||||
<div class="text-900 font-medium text-xl">{{workerInfo.name}}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-blue-100 border-round"
|
||||
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
|
||||
<i class="pi pi-id-card text-blue-500 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <span class="text-green-500 font-medium">24 new </span> -->
|
||||
<span class="text-500">{{workerInfo.sessionId}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">Best Difficulty</span>
|
||||
<div class="text-900 font-medium text-xl">{{workerInfo.bestDifficulty | numberSuffix}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-orange-100 border-round"
|
||||
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
|
||||
<i class="pi pi-star text-orange-500 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-green-500 font-medium">{{workerInfo.bestDifficulty | number}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">Uptime</span>
|
||||
<div class="text-900 font-medium text-xl">{{workerInfo.startTime | dateAgo}}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-cyan-100 border-round"
|
||||
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
|
||||
<i class="pi pi-clock text-cyan-500 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <span class="text-green-500 font-medium">520 </span>-->
|
||||
<span class="text-500">Since {{workerInfo.startTime | date : 'short'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">Comments</span>
|
||||
<div class="text-900 font-medium text-xl">152 Unread</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-purple-100 border-round"
|
||||
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
|
||||
<i class="pi pi-comment text-purple-500 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<span class="text-green-500 font-medium">85 </span>
|
||||
<span class="text-500">responded</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</ng-container>
|
||||
|
||||
|
||||
<div class="col-12" *ngIf="chartData$ | async as chartData">
|
||||
<div class="card">
|
||||
<p-chart type="line" [data]="chartData" [options]="chartOptions"></p-chart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { WorkerGroupComponent } from './worker-group.component';
|
||||
|
||||
describe('WorkerGroupComponent', () => {
|
||||
let component: WorkerGroupComponent;
|
||||
let fixture: ComponentFixture<WorkerGroupComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ WorkerGroupComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(WorkerGroupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
92
src/app/components/worker-group/worker-group.component.ts
Normal file
92
src/app/components/worker-group/worker-group.component.ts
Normal file
@ -0,0 +1,92 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { map, Observable, shareReplay } from 'rxjs';
|
||||
|
||||
import { WorkerService } from '../../services/worker.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-worker-group',
|
||||
templateUrl: './worker-group.component.html',
|
||||
styleUrls: ['./worker-group.component.scss']
|
||||
})
|
||||
export class WorkerGroupComponent {
|
||||
|
||||
public workerInfo$: Observable<any>;
|
||||
|
||||
public chartData$: Observable<any>;
|
||||
|
||||
public chartOptions: any;
|
||||
|
||||
constructor(private workerService: WorkerService, private route: ActivatedRoute) {
|
||||
const documentStyle = getComputedStyle(document.documentElement);
|
||||
const textColor = documentStyle.getPropertyValue('--text-color');
|
||||
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
|
||||
const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
|
||||
|
||||
this.workerInfo$ = this.workerService.getWorkerInfo(this.route.snapshot.params['address'], this.route.snapshot.params['workerName'], this.route.snapshot.params['workerId']).pipe(
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
)
|
||||
|
||||
this.chartData$ = this.workerInfo$.pipe(
|
||||
map((workerInfo: any) => {
|
||||
|
||||
return {
|
||||
labels: workerInfo.chartData.map((d: any) => d.label),
|
||||
datasets: [
|
||||
{
|
||||
label: workerInfo.name,
|
||||
data: workerInfo.chartData.map((d: any) => d.data),
|
||||
fill: false,
|
||||
backgroundColor: documentStyle.getPropertyValue('--bluegray-700'),
|
||||
borderColor: documentStyle.getPropertyValue('--bluegray-700'),
|
||||
tension: .4
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
this.chartOptions = {
|
||||
plugins: {
|
||||
legend: {
|
||||
labels: {
|
||||
color: textColor
|
||||
}
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
unit: 'minute', // Set the unit to 'minute'
|
||||
stepSize: 10, // Set the desired interval between labels in minutes
|
||||
|
||||
},
|
||||
ticks: {
|
||||
color: textColorSecondary
|
||||
},
|
||||
grid: {
|
||||
color: surfaceBorder,
|
||||
drawBorder: false
|
||||
}
|
||||
},
|
||||
y: {
|
||||
ticks: {
|
||||
color: textColorSecondary,
|
||||
callback: (value: number) => value + ' GH/s',
|
||||
},
|
||||
grid: {
|
||||
color: surfaceBorder,
|
||||
drawBorder: false
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -13,14 +13,14 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- <span class="text-green-500 font-medium">24 new </span> -->
|
||||
<span class="text-500">{{workerInfo.id}}</span>
|
||||
<span class="text-500">{{workerInfo.sessionId}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">Best Luck</span>
|
||||
<span class="block text-500 font-medium mb-3">Best Difficulty</span>
|
||||
<div class="text-900 font-medium text-xl">{{workerInfo.bestDifficulty | numberSuffix}}
|
||||
</div>
|
||||
</div>
|
||||
@ -32,23 +32,23 @@
|
||||
<span class="text-green-500 font-medium">{{workerInfo.bestDifficulty | number}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">Customers</span>
|
||||
<div class="text-900 font-medium text-xl">28441</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-cyan-100 border-round"
|
||||
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
|
||||
<i class="pi pi-inbox text-cyan-500 text-xl"></i>
|
||||
<div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
<span class="block text-500 font-medium mb-3">Uptime</span>
|
||||
<div class="text-900 font-medium text-xl">{{workerInfo.startTime | dateAgo}}</div>
|
||||
</div>
|
||||
<div class="flex align-items-center justify-content-center bg-cyan-100 border-round"
|
||||
[ngStyle]="{width: '2.5rem', height: '2.5rem'}">
|
||||
<i class="pi pi-clock text-cyan-500 text-xl"></i>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <span class="text-green-500 font-medium">520 </span>-->
|
||||
<span class="text-500">Since {{workerInfo.startTime | date : 'short'}}</span>
|
||||
</div>
|
||||
<span class="text-green-500 font-medium">520 </span>
|
||||
<span class="text-500">newly registered</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 lg:col-6 xl:col-3">
|
||||
<!-- <div class="col-12 lg:col-6 xl:col-3">
|
||||
<div class="card mb-0">
|
||||
<div class="flex justify-content-between mb-3">
|
||||
<div>
|
||||
|
@ -23,49 +23,19 @@ export class WorkerComponent {
|
||||
const textColorSecondary = documentStyle.getPropertyValue('--text-color-secondary');
|
||||
const surfaceBorder = documentStyle.getPropertyValue('--surface-border');
|
||||
|
||||
this.workerInfo$ = this.workerService.getWorkerInfo(this.route.snapshot.params['address'], this.route.snapshot.params['workerId']).pipe(
|
||||
this.workerInfo$ = this.workerService.getWorkerInfo(this.route.snapshot.params['address'], this.route.snapshot.params['workerName'], this.route.snapshot.params['workerId']).pipe(
|
||||
shareReplay({ bufferSize: 1, refCount: true })
|
||||
)
|
||||
|
||||
this.chartData$ = this.workerInfo$.pipe(
|
||||
map((workerInfo: any) => {
|
||||
|
||||
const GROUP_SIZE = 60;
|
||||
const slice = workerInfo.hashData.length % GROUP_SIZE == 0 ? workerInfo.hashData : workerInfo.hashData.slice(0, workerInfo.hashData.length - workerInfo.hashData.length % GROUP_SIZE)
|
||||
const reducedData = ((slice as any[])
|
||||
.reduce((pre, cur, idx, arr) => {
|
||||
if (idx % GROUP_SIZE != 0) {
|
||||
pre[pre.length - 1].difficulty += cur.difficulty;
|
||||
} else {
|
||||
pre.push(cur);
|
||||
}
|
||||
return pre;
|
||||
}, []) as any[]);
|
||||
|
||||
const labels = reducedData.reduce((pre, cur, idx, arr) => {
|
||||
if (idx == 0) {
|
||||
return pre;
|
||||
}
|
||||
pre.push(new Date(cur.time));
|
||||
return pre;
|
||||
}, []);
|
||||
|
||||
const data = reducedData
|
||||
.reduce((pre, cur, idx, arr) => {
|
||||
if (idx == 0) {
|
||||
return pre;
|
||||
}
|
||||
//hashrate = (nonce_difficulty * 2^32) / time_to_find
|
||||
pre.push((cur.difficulty * 4294967296 / (new Date(cur.time).getTime() - new Date(arr[idx - 1].time).getTime())) / 1000000);
|
||||
return pre;
|
||||
|
||||
}, []);
|
||||
return {
|
||||
labels,
|
||||
labels: workerInfo.chartData.map((d: any) => d.label),
|
||||
datasets: [
|
||||
{
|
||||
label: workerInfo.name,
|
||||
data,
|
||||
data: workerInfo.chartData.map((d: any) => d.data),
|
||||
fill: false,
|
||||
backgroundColor: documentStyle.getPropertyValue('--bluegray-700'),
|
||||
borderColor: documentStyle.getPropertyValue('--bluegray-700'),
|
||||
|
8
src/app/pipes/date-ago.pipe.spec.ts
Normal file
8
src/app/pipes/date-ago.pipe.spec.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { DateAgoPipe } from './date-ago.pipe';
|
||||
|
||||
describe('DateAgoPipe', () => {
|
||||
it('create an instance', () => {
|
||||
const pipe = new DateAgoPipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
37
src/app/pipes/date-ago.pipe.ts
Normal file
37
src/app/pipes/date-ago.pipe.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'dateAgo',
|
||||
pure: true
|
||||
})
|
||||
export class DateAgoPipe implements PipeTransform {
|
||||
|
||||
transform(value: any, args?: any): any {
|
||||
if (value) {
|
||||
const seconds = Math.floor((+new Date() - +new Date(value)) / 1000);
|
||||
if (seconds < 29) // less than 30 seconds ago will show as 'Just now'
|
||||
return 'Just now';
|
||||
const intervals: { [key: string]: number } = {
|
||||
'year': 31536000,
|
||||
'month': 2592000,
|
||||
'week': 604800,
|
||||
'day': 86400,
|
||||
'hour': 3600,
|
||||
'minute': 60,
|
||||
'second': 1
|
||||
};
|
||||
let counter;
|
||||
for (const i in intervals) {
|
||||
counter = Math.floor(seconds / intervals[i]);
|
||||
if (counter > 0)
|
||||
if (counter === 1) {
|
||||
return counter + ' ' + i + ''; // singular (1 day ago)
|
||||
} else {
|
||||
return counter + ' ' + i + 's'; // plural (2 days ago)
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
@ -14,7 +14,7 @@ export class WorkerService {
|
||||
private httpClient: HttpClient
|
||||
) { }
|
||||
|
||||
public getWorkerInfo(address: string, workerId: string): Observable<any> {
|
||||
return this.httpClient.get(`${environment.API_URL}/api/client/${address}/${workerId}`);
|
||||
public getWorkerInfo(address: string, workerName: string, workerId: string): Observable<any> {
|
||||
return this.httpClient.get(`${environment.API_URL}/api/client/${address}/${workerName}/${workerId}`);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user