@@ -31,7 +49,7 @@
{{getSessionCount(worker.name, clientInfo.workers)}} Sessions
- {{getTotalHashRate(worker.name, clientInfo.workers)}} GH/s
+ {{getTotalHashRate(worker.name, clientInfo.workers) | hashSuffix}}
{{getBestDifficulty(worker.name, clientInfo.workers) | numberSuffix}}
@@ -47,7 +65,7 @@
{{worker.sessionId}}
- {{worker.hashRate}} GH/s
+ {{worker.hashRate | hashSuffix}}
{{worker.bestDifficulty | numberSuffix}}
{{worker.startTime | dateAgo}}
diff --git a/src/app/components/dashboard/dashboard.component.ts b/src/app/components/dashboard/dashboard.component.ts
index 2261b33..a324592 100644
--- a/src/app/components/dashboard/dashboard.component.ts
+++ b/src/app/components/dashboard/dashboard.component.ts
@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable, shareReplay } from 'rxjs';
+import { HashSuffixPipe } from '../../pipes/hash-suffix.pipe';
import { ClientService } from '../../services/client.service';
@Component({
@@ -18,6 +19,8 @@ export class DashboardComponent {
public chartOptions: any;
+
+
constructor(private clientService: ClientService, private route: ActivatedRoute) {
this.address = this.route.snapshot.params['address'];
this.clientInfo$ = this.clientService.getClientInfo(this.address).pipe(
@@ -61,18 +64,22 @@ export class DashboardComponent {
label: '2 Hour',
data: hourlyData,
fill: false,
- backgroundColor: documentStyle.getPropertyValue('--primary-color'),
- borderColor: documentStyle.getPropertyValue('--primary-color'),
- tension: .4
+ backgroundColor: documentStyle.getPropertyValue('--yellow-600'),
+ borderColor: documentStyle.getPropertyValue('--yellow-600'),
+ tension: .4,
+ pointRadius: 1,
+ borderWidth: 1
},
{
type: 'line',
label: '10 Minute',
data: data,
fill: false,
- backgroundColor: documentStyle.getPropertyValue('--bluegray-700'),
- borderColor: documentStyle.getPropertyValue('--bluegray-700'),
- tension: .4
+ backgroundColor: documentStyle.getPropertyValue('--primary-color'),
+ borderColor: documentStyle.getPropertyValue('--primary-color'),
+ tension: .4,
+ pointRadius: 1,
+ borderWidth: 1
},
]
@@ -108,7 +115,7 @@ export class DashboardComponent {
y: {
ticks: {
color: textColorSecondary,
- callback: (value: number) => value + ' GH/s',
+ callback: (value: number) => HashSuffixPipe.transform(value)
},
grid: {
color: surfaceBorder,
diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html
new file mode 100644
index 0000000..676640d
--- /dev/null
+++ b/src/app/components/settings/settings.component.html
@@ -0,0 +1,5 @@
+
+
Background Particles
+
+
\ No newline at end of file
diff --git a/src/app/components/settings/settings.component.scss b/src/app/components/settings/settings.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/settings/settings.component.spec.ts b/src/app/components/settings/settings.component.spec.ts
new file mode 100644
index 0000000..c2333ad
--- /dev/null
+++ b/src/app/components/settings/settings.component.spec.ts
@@ -0,0 +1,21 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SettingsComponent } from './settings.component';
+
+describe('SettingsComponent', () => {
+ let component: SettingsComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ declarations: [SettingsComponent]
+ });
+ fixture = TestBed.createComponent(SettingsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts
new file mode 100644
index 0000000..8519d8e
--- /dev/null
+++ b/src/app/components/settings/settings.component.ts
@@ -0,0 +1,24 @@
+import { Component } from '@angular/core';
+
+import { LocalStorageService } from '../../services/local-storage.service';
+
+@Component({
+ selector: 'app-settings',
+ templateUrl: './settings.component.html',
+ styleUrls: ['./settings.component.scss']
+})
+export class SettingsComponent {
+
+ public stateOptions: any[] = [{ label: 'On', value: true }, { label: 'Off', value: false },];
+
+ public value: boolean = true;
+
+ constructor(private localStorageService: LocalStorageService) {
+ this.value = this.localStorageService.getParticles();
+ }
+
+ public particlesChanged(newVal: boolean) {
+ this.localStorageService.setParticles(newVal);
+ this.value = newVal;
+ }
+}
diff --git a/src/app/components/splash/splash.component.html b/src/app/components/splash/splash.component.html
index 1197e5c..d91830d 100644
--- a/src/app/components/splash/splash.component.html
+++ b/src/app/components/splash/splash.component.html
@@ -1,26 +1,42 @@
-
+
-
-
Public Pool
-
+
+
+
Public Pool
+
+
+
+
Fully Open Source Solo Bitcoin Mining Pool
+
+
- stratum+tcp://public-pool-web.airdns.org:21496
+ stratum+tcp://public-pool.airdns.org:21496
username: <your BTC address>.<worker name>, password: x
-
1.8% fee includes donations to open source Bitcoin mining
+
Pleb mining (under 50TH/s miners) mine with NO FEES*
+
+
Miners with > 50TH/s will incur a 1.5% fee, a portion of which will go back into open source Bitcoin mining *
+
No second best.
@@ -32,6 +48,65 @@
+
+
* Based on a machine-by-machine basis
+
+
+
+
+
+
+
+
+
Devices
+
+
+
+ Device
+ Currently Working
+
+
+
+
+ {{ userAgent.userAgent | userAgent }}
+ {{ userAgent.count }}
+
+
+
+
+
+
+
+
+
+
Found Blocks
+
+
+
+ Height
+ Address
+ Worker
+ Session
+
+
+
+
+ {{ block.height }}
+ {{ block.minerAddress }}
+ {{ block.worker }}
+ {{ block.sessionId }}
+
+
+
+
diff --git a/src/app/components/splash/splash.component.scss b/src/app/components/splash/splash.component.scss
index 89cc760..0de12a6 100644
--- a/src/app/components/splash/splash.component.scss
+++ b/src/app/components/splash/splash.component.scss
@@ -2,6 +2,15 @@
min-width: 50%;
}
+.main {
+ margin-top: 60px;
+}
+
.info {
font-weight: bold;
+}
+
+.card {
+ max-width: 1024px;
+ margin: 0 auto;
}
\ No newline at end of file
diff --git a/src/app/components/splash/splash.component.ts b/src/app/components/splash/splash.component.ts
index 040ff0d..46433a8 100644
--- a/src/app/components/splash/splash.component.ts
+++ b/src/app/components/splash/splash.component.ts
@@ -1,6 +1,9 @@
import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';
+import { map, Observable, shareReplay } from 'rxjs';
+import { HashSuffixPipe } from '../../pipes/hash-suffix.pipe';
+import { AppService } from '../../services/app.service';
import { bitcoinAddressValidator } from '../../validators/bitcoin-address.validator';
@Component({
@@ -11,7 +14,83 @@ import { bitcoinAddressValidator } from '../../validators/bitcoin-address.valida
export class SplashComponent {
public address: FormControl;
- constructor() {
+
+ public chartData$: Observable
;
+ public blockData$: Observable;
+ public userAgents$: Observable;
+
+ public chartOptions: any;
+
+ constructor(private appService: AppService) {
+
+ const info$ = this.appService.getInfo().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
+ this.blockData$ = info$.pipe(map(info => info.blockData));
+ this.userAgents$ = info$.pipe(map(info => info.userAgents));
+ this.chartData$ = info$.pipe(
+ map((info: any) => {
+ return {
+ labels: info.chartData.map((d: any) => d.label),
+ datasets: [
+ {
+ label: 'Public-Pool Hashrate',
+ data: info.chartData.map((d: any) => d.data),
+ fill: false,
+ backgroundColor: documentStyle.getPropertyValue('--primary-color'),
+ borderColor: documentStyle.getPropertyValue('--primary-color'),
+ tension: .4,
+ pointRadius: 1,
+ borderWidth: 1
+ }
+ ]
+ }
+ })
+ );
+
this.address = new FormControl(null, bitcoinAddressValidator());
+
+
+
+ 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.chartOptions = {
+ plugins: {
+ legend: {
+ labels: {
+ color: textColor
+ }
+ }
+ },
+ scales: {
+ x: {
+ type: 'time',
+ time: {
+ unit: 'hour', // Set the unit to 'minute'
+ },
+ ticks: {
+ color: textColorSecondary
+ },
+ grid: {
+ color: surfaceBorder,
+ drawBorder: false,
+ display: true
+ }
+ },
+ y: {
+ ticks: {
+ color: textColorSecondary,
+ callback: (value: number) => HashSuffixPipe.transform(value)
+ },
+ grid: {
+ color: surfaceBorder,
+ drawBorder: false
+ }
+ }
+ }
+ };
+
}
}
diff --git a/src/app/components/worker-group/worker-group.component.ts b/src/app/components/worker-group/worker-group.component.ts
index b42587f..5abb11c 100644
--- a/src/app/components/worker-group/worker-group.component.ts
+++ b/src/app/components/worker-group/worker-group.component.ts
@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable, shareReplay } from 'rxjs';
+import { HashSuffixPipe } from '../../pipes/hash-suffix.pipe';
import { WorkerService } from '../../services/worker.service';
@Component({
@@ -37,9 +38,11 @@ export class WorkerGroupComponent {
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
+ backgroundColor: documentStyle.getPropertyValue('--primary-color'),
+ borderColor: documentStyle.getPropertyValue('--primary-color'),
+ tension: .4,
+ pointRadius: 1,
+ borderWidth: 1
}
]
}
@@ -60,9 +63,7 @@ export class WorkerGroupComponent {
x: {
type: 'time',
time: {
- unit: 'minute', // Set the unit to 'minute'
- stepSize: 10, // Set the desired interval between labels in minutes
-
+ unit: 'hour'
},
ticks: {
color: textColorSecondary
@@ -75,7 +76,7 @@ export class WorkerGroupComponent {
y: {
ticks: {
color: textColorSecondary,
- callback: (value: number) => value + ' GH/s',
+ callback: (value: number) => HashSuffixPipe.transform(value)
},
grid: {
color: surfaceBorder,
diff --git a/src/app/components/worker/worker.component.ts b/src/app/components/worker/worker.component.ts
index 019a647..f438796 100644
--- a/src/app/components/worker/worker.component.ts
+++ b/src/app/components/worker/worker.component.ts
@@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map, Observable, shareReplay } from 'rxjs';
+import { HashSuffixPipe } from '../../pipes/hash-suffix.pipe';
import { WorkerService } from '../../services/worker.service';
@Component({
@@ -37,9 +38,11 @@ export class WorkerComponent {
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
+ backgroundColor: documentStyle.getPropertyValue('--primary-color'),
+ borderColor: documentStyle.getPropertyValue('--primary-color'),
+ tension: .4,
+ pointRadius: 1,
+ borderWidth: 1
}
]
}
@@ -60,9 +63,7 @@ export class WorkerComponent {
x: {
type: 'time',
time: {
- unit: 'minute', // Set the unit to 'minute'
- stepSize: 10, // Set the desired interval between labels in minutes
-
+ unit: 'hour'
},
ticks: {
color: textColorSecondary
@@ -75,7 +76,7 @@ export class WorkerComponent {
y: {
ticks: {
color: textColorSecondary,
- callback: (value: number) => value + ' GH/s',
+ callback: (value: number) => HashSuffixPipe.transform(value)
},
grid: {
color: surfaceBorder,
diff --git a/src/app/layout/app.footer.component.html b/src/app/layout/app.footer.component.html
index 3311946..3a6cce8 100644
--- a/src/app/layout/app.footer.component.html
+++ b/src/app/layout/app.footer.component.html
@@ -1,3 +1,3 @@
-
-
+
-
+
\ No newline at end of file
diff --git a/src/app/layout/app.menu.component.ts b/src/app/layout/app.menu.component.ts
index 95a5cfe..63dec8f 100644
--- a/src/app/layout/app.menu.component.ts
+++ b/src/app/layout/app.menu.component.ts
@@ -39,11 +39,12 @@ export class AppMenuComponent implements OnInit {
return [
{
- label: 'Home',
+ label: 'Dashboard',
items: [
- { label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: [params.address] }
+ { label: 'Dashboard', icon: 'pi pi-fw pi-home', routerLink: [params.address] },
+ { label: 'Settings', icon: 'pi pi-fw pi-cog', routerLink: [params.address, 'settings'] }
]
- },
+ }
];
diff --git a/src/app/pipes/hash-suffix.pipe.spec.ts b/src/app/pipes/hash-suffix.pipe.spec.ts
new file mode 100644
index 0000000..52cf57f
--- /dev/null
+++ b/src/app/pipes/hash-suffix.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { HashSuffixPipe } from './hash-suffix.pipe';
+
+describe('HashSuffixPipe', () => {
+ it('create an instance', () => {
+ const pipe = new HashSuffixPipe();
+ expect(pipe).toBeTruthy();
+ });
+});
diff --git a/src/app/pipes/hash-suffix.pipe.ts b/src/app/pipes/hash-suffix.pipe.ts
new file mode 100644
index 0000000..f6a57cf
--- /dev/null
+++ b/src/app/pipes/hash-suffix.pipe.ts
@@ -0,0 +1,33 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'hashSuffix'
+})
+export class HashSuffixPipe implements PipeTransform {
+
+ private static _this = new HashSuffixPipe();
+
+ public static transform(value: number): string {
+ return this._this.transform(value);
+ }
+
+ public transform(value: number): string {
+
+ if (value == null || value < 0) {
+ return '0';
+ }
+
+ const suffixes = [' H/s', ' KH/s', ' MH/s', ' GH/s', ' TH/s', ' PH/s', ' EH/s'];
+
+ let power = Math.floor(Math.log10(value) / 3);
+ if (power < 0) {
+ power = 0;
+ }
+ const scaledValue = value / Math.pow(1000, power);
+ const suffix = suffixes[power];
+
+ return scaledValue.toFixed(1) + suffix;
+ }
+
+
+}
diff --git a/src/app/pipes/number-suffix.pipe.ts b/src/app/pipes/number-suffix.pipe.ts
index 1265ec6..2544e4b 100644
--- a/src/app/pipes/number-suffix.pipe.ts
+++ b/src/app/pipes/number-suffix.pipe.ts
@@ -10,15 +10,18 @@ export class NumberSuffixPipe implements PipeTransform {
const suffixes = ['', 'k', 'M', 'B', 'T', 'P', 'E'];
- if (value === 0) {
+ if (value == null || value < 0) {
return '0';
}
- const power = Math.floor(Math.log10(value) / 3);
+ let power = Math.floor(Math.log10(value) / 3);
+ if (power < 0) {
+ power = 0;
+ }
const scaledValue = value / Math.pow(1000, power);
const suffix = suffixes[power];
- return scaledValue.toFixed(1) + suffix;
+ return scaledValue.toFixed(2) + suffix;
}
}
diff --git a/src/app/pipes/user-agent.pipe.spec.ts b/src/app/pipes/user-agent.pipe.spec.ts
new file mode 100644
index 0000000..fd60137
--- /dev/null
+++ b/src/app/pipes/user-agent.pipe.spec.ts
@@ -0,0 +1,8 @@
+import { UserAgentPipe } from './user-agent.pipe';
+
+describe('UserAgentPipe', () => {
+ it('create an instance', () => {
+ const pipe = new UserAgentPipe();
+ expect(pipe).toBeTruthy();
+ });
+});
diff --git a/src/app/pipes/user-agent.pipe.ts b/src/app/pipes/user-agent.pipe.ts
new file mode 100644
index 0000000..32e138a
--- /dev/null
+++ b/src/app/pipes/user-agent.pipe.ts
@@ -0,0 +1,17 @@
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({
+ name: 'userAgent'
+})
+export class UserAgentPipe implements PipeTransform {
+
+ transform(value: string): string {
+ const valueLowerCase = value.toLowerCase();
+ if (valueLowerCase.includes('bosminer')) {
+ return 'Braiins OS';
+ } else {
+ return value;
+ }
+ }
+
+}
diff --git a/src/app/services/app.service.ts b/src/app/services/app.service.ts
new file mode 100644
index 0000000..8529316
--- /dev/null
+++ b/src/app/services/app.service.ts
@@ -0,0 +1,19 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import { environment } from '../../environments/environment';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class AppService {
+
+ constructor(
+ private httpClient: HttpClient
+ ) { }
+
+ public getInfo() {
+ return this.httpClient.get(`${environment.API_URL}/api/info`) as Observable