Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redo swarm #430

Merged
merged 2 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
<div class="card">

<form [formGroup]="form">
<div class="field grid p-fluid">
<label htmlFor="ip" class="col-12 mb-2 md:col-2 md:mb-0">AxeOS Device IP</label>
<div class="col-12 md:col-10">
<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="ip" formControlName="ip" type="text" />
<button pButton (click)="add()" [disabled]="form.invalid">Add</button>
<input pInputText id="manualAddIp" formControlName="manualAddIp" type="text" />
<button pButton [disabled]="form.invalid" (click)="add()">Add</button>
</p-inputGroup>

</div>
</div>

</form>
</div>
<button style="margin-right: 1rem;" pButton (click)="scanNetwork()" [disabled]="scanning">{{scanning ? 'Scanning...' : 'Automatic Scan'}}</button>
<button pButton severity="secondary" (click)="refreshList()" [disabled]="scanning">Refresh List</button>
<div>
<button pButton (click)="refresh()">Refresh</button>
</div>
<div>
<table cellspacing="0" cellpadding="0" *ngIf="swarm$ | async as swarm">
<table cellspacing="0" cellpadding="0" >
<tr>
<th>IP</th>
<th>Hash Rate</th>
Expand All @@ -31,17 +31,17 @@
<th>Restart</th>
<th>Remove</th>
</tr>
<ng-container *ngFor="let axeOs$ of swarm">
<tr *ngIf="axeOs$ | async as axe">
<td><a [href]="'http://'+axe.ip" target="_blank">{{axe.ip}}</a></td>
<ng-container *ngFor="let axe of swarm">
<tr>
<td><a [href]="'http://'+axe.IP" target="_blank">{{axe.IP}}</a></td>
<td>{{axe.hashRate * 1000000000 | hashSuffix}}</td>
<td>{{axe.uptimeSeconds | dateAgo}}</td>
<td>{{axe.sharesAccepted | number: '1.0-0'}}</td>
<td>{{axe.power | number: '1.2-2'}} <small>W</small> </td>
<td>{{axe.temp}}°<small>C</small></td>
<td>{{axe.bestDiff}}</td>
<td>{{axe.version}}</td>
<td><p-button icon="pi pi-pencil" pp-button (click)="edit(axe)"></p-button></td>
<td><p-button icon="pi pi-pencil" pp-button (click)="edit(axe)"></p-button></td>
<td><p-button icon="pi pi-sync" pp-button severity="danger" (click)="restart(axe)"></p-button></td>
<td><p-button icon="pi pi-trash" pp-button severity="secondary" (click)="remove(axe)"></p-button></td>
</tr>
Expand All @@ -52,6 +52,6 @@
<div class="modal-backdrop" *ngIf="showEdit" (click)="showEdit = false"></div>
<div class="modal card" *ngIf="showEdit">
<div class="close" (click)="showEdit = false">&#10006;</div>
<h1>{{selectedAxeOs.ip}}</h1>
<app-edit [uri]="'http://' + selectedAxeOs.ip"></app-edit>
<h1>{{selectedAxeOs.IP}}</h1>
<app-edit [uri]="'http://' + selectedAxeOs.IP"></app-edit>
</div>
189 changes: 106 additions & 83 deletions main/http_server/axe-os/src/app/components/swarm/swarm.component.ts
Original file line number Diff line number Diff line change
@@ -1,99 +1,114 @@
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { BehaviorSubject, catchError, combineLatest, forkJoin, map, Observable, of, startWith, switchMap } from 'rxjs';
import { BehaviorSubject, catchError, combineLatest, debounce, debounceTime, forkJoin, from, interval, map, mergeAll, mergeMap, Observable, of, startWith, switchMap, take, timeout, toArray } from 'rxjs';
import { LocalStorageService } from 'src/app/local-storage.service';
import { SystemService } from 'src/app/services/system.service';

const SWARM_DATA = 'SWARM_DATA'
@Component({
selector: 'app-swarm',
templateUrl: './swarm.component.html',
styleUrls: ['./swarm.component.scss']
})
export class SwarmComponent {
export class SwarmComponent implements OnInit {

public form: FormGroup;

public swarm$: Observable<Observable<any>[]>;

public refresh$: BehaviorSubject<null> = new BehaviorSubject(null);
public swarm: any[] = [];

public selectedAxeOs: any = null;
public showEdit = false;

public form: FormGroup;

public scanning = false;

constructor(
private fb: FormBuilder,
private systemService: SystemService,
private toastr: ToastrService
private toastr: ToastrService,
private localStorageService: LocalStorageService,
private httpClient: HttpClient
) {
this.form = this.fb.group({
ip: [null, [Validators.required, Validators.pattern('(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)')]]
});

this.swarm$ = this.systemService.getSwarmInfo().pipe(
map(swarmInfo => {
return swarmInfo.map(({ ip }) => {
// Make individual API calls for each IP
return this.refresh$.pipe(
switchMap(() => {
return this.systemService.getInfo(`http://${ip}`);
})
).pipe(
startWith({ ip }),
map(info => {
return {
ip,
...info
};
}),
catchError(error => {
return of({ ip, error: true });
})
);
});
})
);

this.form = this.fb.group({
manualAddIp: [null, [Validators.required, Validators.pattern('(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)')]]
})

}
ngOnInit(): void {
const swarmData = this.localStorageService.getObject(SWARM_DATA);
console.log(swarmData);
if (swarmData == null) {
this.scanNetwork();
//this.swarm$ = this.scanNetwork('192.168.1.23', '255.255.255.0').pipe(take(1));
} else {
this.swarm = swarmData;
}
}


public add() {
const newIp = this.form.value.ip;

combineLatest([this.systemService.getSwarmInfo('http://' + newIp), this.systemService.getSwarmInfo()]).pipe(
switchMap(([newSwarmInfo, existingSwarmInfo]) => {

if (existingSwarmInfo.length < 1) {
existingSwarmInfo.push({ ip: window.location.host });
}


const swarmUpdate = existingSwarmInfo.map(({ ip }) => {
return this.systemService.updateSwarm('http://' + ip, [{ ip: newIp }, ...newSwarmInfo, ...existingSwarmInfo])
});
private ipToInt(ip: string): number {
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0) >>> 0;
}

const newAxeOs = this.systemService.updateSwarm('http://' + newIp, [{ ip: newIp }, ...existingSwarmInfo])
private intToIp(int: number): string {
return `${(int >>> 24) & 255}.${(int >>> 16) & 255}.${(int >>> 8) & 255}.${int & 255}`;
}

return forkJoin([newAxeOs, ...swarmUpdate]);
private calculateIpRange(ip: string, netmask: string): { start: number, end: number } {
const ipInt = this.ipToInt(ip);
const netmaskInt = this.ipToInt(netmask);
const network = ipInt & netmaskInt;
const broadcast = network | ~netmaskInt;
return { start: network + 1, end: broadcast - 1 };
}

})
).subscribe({
next: () => {
this.toastr.success('Success!', 'Saved.');
window.location.reload();
},
error: (err) => {
this.toastr.error('Error.', `Could not save. ${err.message}`);
scanNetwork() {
this.scanning = true;

const { start, end } = this.calculateIpRange(window.location.hostname, '255.255.255.0');
const ips = Array.from({ length: end - start + 1 }, (_, i) => this.intToIp(start + i));
from(ips).pipe(
mergeMap(ipAddr =>
this.httpClient.get(`http://${ipAddr}/api/system/info`).pipe(
map(result => {
return {
IP: ipAddr,
...result
}
}),
timeout(5000), // Set the timeout to 1 second
catchError(error => {
//console.error(`Request to ${ipAddr}/api/system/info failed or timed out`, error);
return []; // Return an empty result or handle as desired
})
),
256 // Limit concurrency to avoid overload
),
toArray() // Collect all results into a single array
).pipe(take(1)).subscribe({
next: (result) => {
this.swarm = result;
this.localStorageService.setObject(SWARM_DATA, this.swarm);
},
complete: () => {
this.form.reset();
this.scanning = false;
}
});

}

public refresh() {
this.refresh$.next(null);
public add() {
const newIp = this.form.value.manualAddIp;

this.systemService.getInfo(`http://${newIp}`).subscribe((res) => {
if (res.ASICModel) {
this.swarm.push({ IP: newIp, ...res });
this.localStorageService.setObject(SWARM_DATA, this.swarm);
}
});
}

public edit(axe: any) {
Expand All @@ -102,38 +117,46 @@ export class SwarmComponent {
}

public restart(axe: any) {
this.systemService.restart(`http://${axe.ip}`).subscribe(res => {
this.systemService.restart(`http://${axe.IP}`).subscribe(res => {

});
this.toastr.success('Success!', 'Bitaxe restarted');
}

public remove(axeOs: any) {
this.systemService.getSwarmInfo().pipe(
switchMap((swarmInfo) => {

const newSwarm = swarmInfo.filter((s: any) => s.ip != axeOs.ip);

const swarmUpdate = newSwarm.map(({ ip }) => {
return this.systemService.updateSwarm('http://' + ip, newSwarm)
});

const removedAxeOs = this.systemService.updateSwarm('http://' + axeOs.ip, []);
this.swarm = this.swarm.filter(axe => axe.IP != axeOs.IP);
this.localStorageService.setObject(SWARM_DATA, this.swarm);
}

return forkJoin([removedAxeOs, ...swarmUpdate]);
})
).subscribe({
next: () => {
this.toastr.success('Success!', 'Saved.');
window.location.reload();
},
error: (err) => {
this.toastr.error('Error.', `Could not save. ${err.message}`);
public refreshList() {
const ips = this.swarm.map(axeOs => axeOs.IP);

from(ips).pipe(
mergeMap(ipAddr =>
this.httpClient.get(`http://${ipAddr}/api/system/info`).pipe(
map(result => {
return {
IP: ipAddr,
...result
}
}),
timeout(5000),
catchError(error => {
return this.swarm.find(axeOs => axeOs.IP == ipAddr);
})
),
256 // Limit concurrency to avoid overload
),
toArray() // Collect all results into a single array
).pipe(take(1)).subscribe({
next: (result) => {
this.swarm = result;
this.localStorageService.setObject(SWARM_DATA, this.swarm);
},
complete: () => {
this.form.reset();
}
});

}

}
16 changes: 16 additions & 0 deletions main/http_server/axe-os/src/app/local-storage.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { LocalStorageService } from './local-storage.service';

describe('LocalStorageService', () => {
let service: LocalStorageService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LocalStorageService);
});

it('should be created', () => {
expect(service).toBeTruthy();
});
});
37 changes: 37 additions & 0 deletions main/http_server/axe-os/src/app/local-storage.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root'
})
export class LocalStorageService {

constructor() { }

setItem(key: string, value: any) {
localStorage.setItem(key, value);
}

getItem(key: string): any {
return localStorage.getItem(key);
}

setBool(key: string, value: boolean) {
localStorage.setItem(key, String(value));
}

getBool(key: string): boolean {
return localStorage.getItem(key) === 'true';
}

setObject(key: string, value: object) {
localStorage.setItem(key, JSON.stringify(value));
}

getObject(key: string): any | null{
const item = localStorage.getItem(key);
if(item == null || item.length < 1){
return null;
}
return JSON.parse(item);
}
}
Loading
Loading