This commit is contained in:
Ben Wilson 2023-06-24 12:54:21 -04:00
parent 48516e468e
commit 3ba7ef8ca9
14 changed files with 355 additions and 62 deletions

View File

@ -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
}
]
}
]
}
]

View File

@ -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,

View File

@ -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>

View File

@ -0,0 +1,3 @@
.worker-name {
line-height: 42px;
}

View File

@ -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);
}
}

View 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>

View File

@ -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();
});
});

View 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
}
}
}
};
}
}

View File

@ -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>

View File

@ -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'),

View File

@ -0,0 +1,8 @@
import { DateAgoPipe } from './date-ago.pipe';
describe('DateAgoPipe', () => {
it('create an instance', () => {
const pipe = new DateAgoPipe();
expect(pipe).toBeTruthy();
});
});

View 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;
}
}

View File

@ -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}`);
}
}