swarm refreshing

This commit is contained in:
Ben 2023-11-25 15:58:16 -05:00
parent c1d92c7f13
commit 1fae8781d4
14 changed files with 431 additions and 229 deletions

View File

@ -1,8 +1,8 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EditComponent } from './components/edit/edit.component';
import { HomeComponent } from './components/home/home.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SwarmComponent } from './components/swarm/swarm.component';
const routes: Routes = [
@ -11,8 +11,8 @@ const routes: Routes = [
component: HomeComponent
},
{
path: 'edit',
component: EditComponent
path: 'settings',
component: SettingsComponent
},
{
path: 'swarm',

View File

@ -9,7 +9,7 @@
<div class="tab-container">
<div class="tab" [routerLink]="['/']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">Home
</div>
<div class="tab" [routerLink]="['edit']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">
<div class="tab" [routerLink]="['settings']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">
Settings
</div>
<div class="tab" [routerLink]="['swarm']" routerLinkActive="active" [routerLinkActiveOptions]="{exact:true}">

View File

@ -48,7 +48,7 @@
}
.content {
padding-top: 26px;
padding-top: 50px;
padding-left: 5vw;
padding-right: 5vw;
border-left: 1px solid #304562;

View File

@ -12,24 +12,28 @@ import { EditComponent } from './components/edit/edit.component';
import { HeaderComponent } from './components/header/header.component';
import { HomeComponent } from './components/home/home.component';
import { LoadingComponent } from './components/loading/loading.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SwarmComponent } from './components/swarm/swarm.component';
import { ANSIPipe } from './pipes/ansi.pipe';
import { DateAgoPipe } from './pipes/date-ago.pipe';
import { SwarmComponent } from './components/swarm/swarm.component';
const components = [
AppComponent,
HeaderComponent
HeaderComponent,
EditComponent,
HomeComponent,
LoadingComponent,
SettingsComponent
];
@NgModule({
declarations: [
...components,
EditComponent,
HomeComponent,
LoadingComponent,
ANSIPipe,
DateAgoPipe,
SwarmComponent
SwarmComponent,
SettingsComponent
],
imports: [
BrowserModule,

View File

@ -1,148 +1,128 @@
<div class="card">
<h2>Settings</h2>
<ng-container *ngIf="form != null">
<form [formGroup]="form">
<div class="form-group">
<label>Flip Screen</label>
<input formControlName="flipscreen" type="checkbox">
</div>
<!-- <div class="form-group">
<label>Invert Screen</label>
<input formControlName="invertscreen" type="checkbox">
</div> -->
<div class="form-group">
<label>WiFi SSID: </label>
<input formControlName="ssid" type="text">
</div>
<div class="form-group">
<label>WiFi Password: </label>
<input formControlName="wifiPass" type="password">
</div>
<div class="form-group">
<label>Stratum URL: (Do not include 'stratum+tcp://' or port</label>
<input formControlName="stratumURL" type="text">
</div>
<div class="form-group">
<label>Stratum Port:</label>
<input formControlName="stratumPort" type="number">
</div>
<div class="form-group">
<label>Stratum User: </label>
<input formControlName="stratumUser" type="text">
</div>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1366">
<div class="form-group">
<label>Frequency </label>
<select formControlName="frequency">
<option value="400">400</option>
<option value="425">425</option>
<option value="450">450</option>
<option value="475">475</option>
<option value="485">485 (default)</option>
<option value="500">500</option>
<option value="525">525</option>
<option value="550">550</option>
<option value="575">575</option>
</select>
</div>
<div class="form-group">
<label>Core Voltage </label>
<select formControlName="coreVoltage">
<option value="1100">1100</option>
<option value="1150">1150</option>
<option value="1200">1200 (default)</option>
<option value="1250">1250</option>
<option value="1300">1300</option>
</select>
</div>
</ng-container>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1397">
<div class="form-group">
<label>Frequency </label>
<select formControlName="frequency">
<option value="400">400</option>
<option value="425">425 (default)</option>
<option value="450">450</option>
<option value="475">475</option>
<option value="485">485</option>
<option value="500">500</option>
<option value="525">525</option>
<option value="550">550</option>
<option value="575">575</option>
</select>
</div>
<div class="form-group">
<label>Core Voltage </label>
<select formControlName="coreVoltage">
<option value="1300">1300</option>
<option value="1350">1350</option>
<option value="1400">1400 (default)</option>
<option value="1450">1450</option>
<option value="1500">1500</option>
</select>
</div>
</ng-container>
<ng-container *ngIf="devToolsOpen === true">
<div class="form-group">
<label>Frequency </label>
<input formControlName="frequency" type="number">
</div>
<div class="form-group">
<label>Core Voltage</label>
<input formControlName="coreVoltage" type="number">
</div>
</ng-container>
<ng-container *ngIf="form != null">
<form [formGroup]="form">
<div class="form-group">
<label>Flip Screen</label>
<input formControlName="flipscreen" type="checkbox">
</div>
<!-- <div class="form-group">
<label>Invert Screen</label>
<input formControlName="invertscreen" type="checkbox">
</div> -->
<div class="form-group">
<label>WiFi SSID: </label>
<input formControlName="ssid" type="text">
</div>
<div class="form-group">
<label>WiFi Password: </label>
<input formControlName="wifiPass" type="password">
</div>
<div class="form-group">
<label>Stratum URL: (Do not include 'stratum+tcp://' or port</label>
<input formControlName="stratumURL" type="text">
</div>
<div class="form-group">
<label>Stratum Port:</label>
<input formControlName="stratumPort" type="number">
</div>
<div class="form-group">
<label>Stratum User: </label>
<input formControlName="stratumUser" type="text">
</div>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1366">
<div class="form-group">
<label>Invert Fan Polarity</label>
<input formControlName="invertfanpolarity" type="checkbox">
<label>Frequency </label>
<select formControlName="frequency">
<option value="400">400</option>
<option value="425">425</option>
<option value="450">450</option>
<option value="475">475</option>
<option value="485">485 (default)</option>
<option value="500">500</option>
<option value="525">525</option>
<option value="550">550</option>
<option value="575">575</option>
</select>
</div>
<div class="form-group">
<label>Automatic Fan Control</label>
<input formControlName="autofanspeed" type="checkbox">
<label>Core Voltage </label>
<select formControlName="coreVoltage">
<option value="1100">1100</option>
<option value="1150">1150</option>
<option value="1200">1200 (default)</option>
<option value="1250">1250</option>
<option value="1300">1300</option>
</select>
</div>
</ng-container>
<ng-container *ngIf="!devToolsOpen && ASICModel == eASICModel.BM1397">
<div class="form-group">
<label>Frequency </label>
<select formControlName="frequency">
<option value="400">400</option>
<option value="425">425 (default)</option>
<option value="450">450</option>
<option value="475">475</option>
<option value="485">485</option>
<option value="500">500</option>
<option value="525">525</option>
<option value="550">550</option>
<option value="575">575</option>
</select>
</div>
<div class="form-group" *ngIf="form.controls['autofanspeed'].value != true">
<label>Fan Speed {{form.controls['fanspeed'].value}}%
<b *ngIf="form.controls['fanspeed'].value < 33" style="color:red">Danger: Could Cause
Overheating</b> <b *ngIf="form.controls['fanspeed'].value == 100" style="color: #F2A900">S19
Simulator</b></label>
<input formControlName="fanspeed" type="range" [min]="0" [max]="100">
<div class="form-group">
<label>Core Voltage </label>
<select formControlName="coreVoltage">
<option value="1300">1300</option>
<option value="1350">1350</option>
<option value="1400">1400 (default)</option>
<option value="1450">1450</option>
<option value="1500">1500</option>
</select>
</div>
<div class="mt-2">
<button [disabled]="form.invalid" (click)="updateSystem()" class="btn btn-primary mr-2">Save</button>
<b style="line-height: 34px;">You must restart this device after saving for changes to take effect.</b>
</ng-container>
<ng-container *ngIf="devToolsOpen === true">
<div class="form-group">
<label>Frequency </label>
<input formControlName="frequency" type="number">
</div>
</form>
</ng-container>
<div class="form-group">
<label>Core Voltage</label>
<input formControlName="coreVoltage" type="number">
</div>
</ng-container>
<div class="form-group">
<label>Invert Fan Polarity</label>
<input formControlName="invertfanpolarity" type="checkbox">
</div>
</div>
<div class="form-group">
<label>Automatic Fan Control</label>
<input formControlName="autofanspeed" type="checkbox">
</div>
<div class="card">
<h2>Update Firmware <span *ngIf="firmwareUpdateProgress != null">{{firmwareUpdateProgress}}%</span></h2>
<input type="file" id="file" (change)="otaUpdate($event)" accept=".bin">
<br>
<small>(esp-miner.bin)</small>
</div>
<div class="card">
<h2>Update Website <span *ngIf="websiteUpdateProgress != null">{{websiteUpdateProgress}}%</span></h2>
<input type="file" id="file" (change)="otaWWWUpdate($event)" accept=".bin">
<br>
<small>(www.bin)</small>
</div>
<div class="form-group" *ngIf="form.controls['autofanspeed'].value != true">
<label>Fan Speed {{form.controls['fanspeed'].value}}%
<b *ngIf="form.controls['fanspeed'].value < 33" style="color:red">Danger: Could Cause
Overheating</b> <b *ngIf="form.controls['fanspeed'].value == 100" style="color: #F2A900">S19
Simulator</b></label>
<input formControlName="fanspeed" type="range" [min]="0" [max]="100">
</div>
<div class="mt-2">
<button [disabled]="form.invalid" (click)="updateSystem()" class="btn btn-primary mr-2">Save</button>
<b style="line-height: 34px;">You must restart this device after saving for changes to take effect.</b>
</div>
</form>
</ng-container>

View File

@ -1,4 +1,4 @@
import { HttpErrorResponse, HttpEventType } from '@angular/common/http';
import { HttpErrorResponse } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
@ -111,81 +111,4 @@ export class EditComponent {
});
}
otaUpdate(event: any) {
const file = event.target?.files.item(0) as File;
if (file.name != 'esp-miner.bin') {
this.toastrService.error('Incorrect file, looking for esp-miner.bin.', 'Error');
return;
}
this.systemService.performOTAUpdate(file)
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.firmwareUpdateProgress = Math.round((event.loaded / (event.total as number)) * 100);
} else if (event.type === HttpEventType.Response) {
if (event.ok) {
this.toastrService.success('Firmware updated', 'Success!');
} else {
this.toastrService.error(event.statusText, 'Error');
}
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
},
complete: () => {
this.firmwareUpdateProgress = null;
}
});
}
otaWWWUpdate(event: any) {
const file = event.target?.files.item(0) as File;
if (file.name != 'www.bin') {
this.toastrService.error('Incorrect file, looking for www.bin.', 'Error');
return;
}
this.systemService.performWWWOTAUpdate(file)
.pipe(
this.loadingService.lockUIUntilComplete(),
).subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.websiteUpdateProgress = Math.round((event.loaded / (event.total as number)) * 100);
} else if (event.type === HttpEventType.Response) {
if (event.ok) {
setTimeout(() => {
this.toastrService.success('Website updated', 'Success!');
window.location.reload();
}, 1000);
} else {
this.toastrService.error(event.statusText, 'Error');
}
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
},
complete: () => {
this.websiteUpdateProgress = null;
}
});
}
public restart() {
this.systemService.restart().subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
}

View File

@ -0,0 +1,21 @@
<div class="card">
<h2>Settings</h2>
<app-edit></app-edit>
</div>
<div class="card">
<h2>Update Firmware <span *ngIf="firmwareUpdateProgress != null">{{firmwareUpdateProgress}}%</span></h2>
<input type="file" id="file" (change)="otaUpdate($event)" accept=".bin">
<br>
<small>(esp-miner.bin)</small>
</div>
<div class="card">
<h2>Update Website <span *ngIf="websiteUpdateProgress != null">{{websiteUpdateProgress}}%</span></h2>
<input type="file" id="file" (change)="otaWWWUpdate($event)" accept=".bin">
<br>
<small>(www.bin)</small>
</div>

View File

@ -0,0 +1,31 @@
input[type="text"],
input[type="number"],
input[type="password"],
input[type="range"] {
min-width: 250px;
max-width: 90%;
}
select {
min-width: 268px;
max-width: 90%;
}
.restart {
margin-bottom: 1.5rem;
}
@media only screen and (min-width:900px) {
input[type="text"],
input[type="password"],
input[type="number"],
input[type="range"] {
min-width: 500px
}
select {
min-width: 518px
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SettingsComponent } from './settings.component';
describe('SettingsComponent', () => {
let component: SettingsComponent;
let fixture: ComponentFixture<SettingsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SettingsComponent]
});
fixture = TestBed.createComponent(SettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,191 @@
import { HttpErrorResponse, HttpEventType } from '@angular/common/http';
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { startWith } from 'rxjs';
import { LoadingService } from 'src/app/services/loading.service';
import { SystemService } from 'src/app/services/system.service';
import { eASICModel } from 'src/models/enum/eASICModel';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent {
public form!: FormGroup;
public firmwareUpdateProgress: number | null = null;
public websiteUpdateProgress: number | null = null;
public devToolsOpen: boolean = false;
public eASICModel = eASICModel;
public ASICModel!: eASICModel;
constructor(
private fb: FormBuilder,
private systemService: SystemService,
private toastr: ToastrService,
private toastrService: ToastrService,
private loadingService: LoadingService
) {
window.addEventListener('resize', this.checkDevTools);
this.checkDevTools();
this.systemService.getInfo()
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe(info => {
this.ASICModel = info.ASICModel;
this.form = this.fb.group({
flipscreen: [info.flipscreen == 1],
invertscreen: [info.invertscreen == 1],
stratumURL: [info.stratumURL, [
Validators.required,
Validators.pattern(/^(?!.*stratum\+tcp:\/\/).*$/),
Validators.pattern(/^[^:]*$/),
]],
stratumPort: [info.stratumPort, [
Validators.required,
Validators.pattern(/^[^:]*$/),
Validators.min(0),
Validators.max(65353)
]],
stratumUser: [info.stratumUser, [Validators.required]],
ssid: [info.ssid, [Validators.required]],
wifiPass: [info.wifiPass, [Validators.required]],
coreVoltage: [info.coreVoltage, [Validators.required]],
frequency: [info.frequency, [Validators.required]],
autofanspeed: [info.autofanspeed == 1, [Validators.required]],
invertfanpolarity: [info.invertfanpolarity == 1, [Validators.required]],
fanspeed: [info.fanspeed, [Validators.required]],
});
this.form.controls['autofanspeed'].valueChanges.pipe(
startWith(this.form.controls['autofanspeed'].value)
).subscribe(autofanspeed => {
if (autofanspeed) {
this.form.controls['fanspeed'].disable();
} else {
this.form.controls['fanspeed'].enable();
}
});
});
}
private checkDevTools = () => {
if (
window.outerWidth - window.innerWidth > 160 ||
window.outerHeight - window.innerHeight > 160
) {
this.devToolsOpen = true;
} else {
this.devToolsOpen = false;
}
};
public updateSystem() {
const form = this.form.getRawValue();
form.frequency = parseInt(form.frequency);
form.coreVoltage = parseInt(form.coreVoltage);
// bools to ints
form.flipscreen = form.flipscreen == true ? 1 : 0;
form.invertscreen = form.invertscreen == true ? 1 : 0;
form.invertfanpolarity = form.invertfanpolarity == true ? 1 : 0;
form.autofanspeed = form.autofanspeed == true ? 1 : 0;
this.systemService.updateSystem(form)
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe({
next: () => {
this.toastr.success('Success!', 'Saved.');
},
error: (err: HttpErrorResponse) => {
this.toastr.error('Error.', `Could not save. ${err.message}`);
}
});
}
otaUpdate(event: any) {
const file = event.target?.files.item(0) as File;
if (file.name != 'esp-miner.bin') {
this.toastrService.error('Incorrect file, looking for esp-miner.bin.', 'Error');
return;
}
this.systemService.performOTAUpdate(file)
.pipe(this.loadingService.lockUIUntilComplete())
.subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.firmwareUpdateProgress = Math.round((event.loaded / (event.total as number)) * 100);
} else if (event.type === HttpEventType.Response) {
if (event.ok) {
this.toastrService.success('Firmware updated', 'Success!');
} else {
this.toastrService.error(event.statusText, 'Error');
}
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
},
complete: () => {
this.firmwareUpdateProgress = null;
}
});
}
otaWWWUpdate(event: any) {
const file = event.target?.files.item(0) as File;
if (file.name != 'www.bin') {
this.toastrService.error('Incorrect file, looking for www.bin.', 'Error');
return;
}
this.systemService.performWWWOTAUpdate(file)
.pipe(
this.loadingService.lockUIUntilComplete(),
).subscribe({
next: (event) => {
if (event.type === HttpEventType.UploadProgress) {
this.websiteUpdateProgress = Math.round((event.loaded / (event.total as number)) * 100);
} else if (event.type === HttpEventType.Response) {
if (event.ok) {
setTimeout(() => {
this.toastrService.success('Website updated', 'Success!');
window.location.reload();
}, 1000);
} else {
this.toastrService.error(event.statusText, 'Error');
}
}
},
error: (err) => {
this.toastrService.error(event.statusText, 'Error');
},
complete: () => {
this.websiteUpdateProgress = null;
}
});
}
public restart() {
this.systemService.restart().subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
}

View File

@ -3,6 +3,9 @@
<input formControlName="ip" type="text">
<button style="margin-left: 15px;" class="btn btn-primary" (click)="add()">Add</button>
</form>
<div>
<button class="btn btn-primary" (click)="refresh()">Refresh</button>
</div>
<div>
<table cellspacing="0" cellpadding="0" *ngIf="swarm$ | async as swarm">
<tr>
@ -13,18 +16,22 @@
<th>Power</th>
<th>Temp</th>
<th>Best Difficulty</th>
<th>Edit</th>
<th>Restart</th>
<th>Remove</th>
</tr>
<ng-container *ngFor="let axeOs$ of swarm">
<tr *ngIf="axeOs$ | async as axe">
<td>{{axe.ip}}</td>
<td><a [href]="'http://'+axe.ip" target="_blank">{{axe.ip}}</a></td>
<td>{{axe.hashRate | number: '1.2-2'}} <small>Gh/s</small></td>
<td>{{axe.uptimeSeconds | dateAgo}}</td>
<td>{{axe.sharesAccepted}}</td>
<td>{{axe.power | number: '1.2-2'}} <small>W</small> </td>
<td>{{axe.temp}} <small>C</small></td>
<td>{{axe.bestDiff}}</td>
<td><button class="btn btn-secondary" (click)="remove(axe)">X</button></td>
<td><button class="btn btn-primary" (click)="edit(axe)">&#9998;</button></td>
<td><button class="btn btn-danger" (click)="restart(axe)">&#9850;</button></td>
<td><button class="btn btn-secondary" (click)="remove(axe)">&#10006;</button></td>
</tr>
</ng-container>
</table>

View File

@ -19,4 +19,8 @@ th,
td {
padding: 1rem 1rem;
border-bottom: 1px solid #304562;
}
a {
color: white;
}

View File

@ -1,7 +1,7 @@
import { Component } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { catchError, map, Observable, of, startWith, switchMap } from 'rxjs';
import { BehaviorSubject, catchError, map, Observable, of, startWith, switchMap } from 'rxjs';
import { SystemService } from 'src/app/services/system.service';
@Component({
@ -15,6 +15,8 @@ export class SwarmComponent {
public swarm$: Observable<Observable<any>[]>;
public refresh$: BehaviorSubject<null> = new BehaviorSubject(null);
constructor(
private fb: FormBuilder,
private systemService: SystemService,
@ -28,7 +30,11 @@ export class SwarmComponent {
map(swarmInfo => {
return swarmInfo.map(({ ip }) => {
// Make individual API calls for each IP
return this.systemService.getInfo(`http://${ip}`).pipe(
return this.refresh$.pipe(
switchMap(() => {
return this.systemService.getInfo(`http://${ip}`);
})
).pipe(
startWith({ ip }),
map(info => {
return {
@ -70,6 +76,20 @@ export class SwarmComponent {
}
public refresh() {
this.refresh$.next(null);
}
public edit(axe: any) {
}
public restart(axe: any) {
this.systemService.restart(`http://${axe.ip}`).subscribe(res => {
});
this.toastr.success('Success!', 'Bitaxe restarted');
}
public remove(axe: any) {
this.systemService.getSwarmInfo().pipe(
switchMap((swarm: any) => {

View File

@ -15,9 +15,9 @@ export class SystemService {
private httpClient: HttpClient
) { }
public getInfo(ip: string = ''): Observable<ISystemInfo> {
public getInfo(uri: string = ''): Observable<ISystemInfo> {
if (environment.production) {
return this.httpClient.get(`${ip}/api/system/info`) as Observable<ISystemInfo>;
return this.httpClient.get(`${uri}/api/system/info`) as Observable<ISystemInfo>;
} else {
return of(
{
@ -53,8 +53,8 @@ export class SystemService {
}
}
public restart() {
return this.httpClient.post(`/api/system/restart`, {});
public restart(uri: string = '') {
return this.httpClient.post(`${uri}/api/system/restart`, {});
}
public updateSystem(update: any) {