11import { HttpErrorResponse , HttpEventType } from '@angular/common/http' ;
2- import { Component , ViewChild } from '@angular/core' ;
2+ import { Component , OnDestroy , ViewChild } from '@angular/core' ;
33import { FormBuilder , FormGroup , Validators } from '@angular/forms' ;
44import { ToastrService } from 'ngx-toastr' ;
55import { FileUploadHandlerEvent , FileUpload } from 'primeng/fileupload' ;
6- import { map , Observable , shareReplay , startWith } from 'rxjs' ;
7- import { GithubUpdateService } from 'src/app/services/github-update.service' ;
6+ import { map , Observable , shareReplay , startWith , Subscription } from 'rxjs' ;
7+ import { GithubRelease , GithubUpdateService } from 'src/app/services/github-update.service' ;
88import { LoadingService } from 'src/app/services/loading.service' ;
99import { SystemService } from 'src/app/services/system.service' ;
1010import { eASICModel } from 'src/models/enum/eASICModel' ;
@@ -14,7 +14,7 @@ import { eASICModel } from 'src/models/enum/eASICModel';
1414 templateUrl : './settings.component.html' ,
1515 styleUrls : [ './settings.component.scss' ]
1616} )
17- export class SettingsComponent {
17+ export class SettingsComponent implements OnDestroy {
1818
1919 public form ! : FormGroup ;
2020
@@ -27,6 +27,15 @@ export class SettingsComponent {
2727
2828 public checkLatestRelease : boolean = false ;
2929 public latestRelease$ : Observable < any > ;
30+ public releases$ ! : Observable < GithubRelease [ ] > ;
31+ public selectedRelease : GithubRelease | null = null ;
32+
33+ // GitHub OTA state
34+ public isGithubOTA : boolean = false ;
35+ public githubOTAStep : string = 'idle' ;
36+ public githubOTAProgress : number = 0 ;
37+ private otaPollSub : Subscription | null = null ;
38+ private rebootCheckSub : Subscription | null = null ;
3039
3140 public info$ : Observable < any > ;
3241
@@ -86,6 +95,14 @@ export class SettingsComponent {
8695 } ) ;
8796
8897 }
98+
99+ ngOnDestroy ( ) {
100+ this . stopOTAPolling ( ) ;
101+ if ( this . rebootCheckSub ) {
102+ this . rebootCheckSub . unsubscribe ( ) ;
103+ }
104+ }
105+
89106 public updateSystem ( ) {
90107
91108 const form = this . form . getRawValue ( ) ;
@@ -191,6 +208,132 @@ export class SettingsComponent {
191208 } ) ;
192209 }
193210
211+ public loadReleases ( ) {
212+ this . checkLatestRelease = true ;
213+ this . releases$ = this . githubUpdateService . getReleases ( ) ;
214+ }
215+
216+ public onReleaseSelected ( release : GithubRelease ) {
217+ this . selectedRelease = release ;
218+ }
219+
220+ public getStepLabel ( step : string ) : string {
221+ switch ( step ) {
222+ case 'downloading_fw' : return 'Downloading firmware...' ;
223+ case 'flashing_fw' : return 'Downloading & flashing firmware...' ;
224+ case 'downloading_www' : return 'Downloading website...' ;
225+ case 'flashing_www' : return 'Flashing website...' ;
226+ case 'rebooting' : return 'Rebooting...' ;
227+ case 'error' : return 'Error' ;
228+ default : return 'Starting...' ;
229+ }
230+ }
231+
232+ public installFromGithub ( ) {
233+ if ( ! this . selectedRelease ) return ;
234+
235+ const fwUrl = this . githubUpdateService . findAssetUrl ( this . selectedRelease , 'bitforgeos.bin' ) ;
236+ const wwwUrl = this . githubUpdateService . findAssetUrl ( this . selectedRelease , 'www.bin' ) ;
237+
238+ if ( ! fwUrl || ! wwwUrl ) {
239+ this . toastrService . error ( 'Release is missing bitforgeos.bin or www.bin assets' , 'Error' ) ;
240+ return ;
241+ }
242+
243+ this . isGithubOTA = true ;
244+ this . githubOTAStep = 'idle' ;
245+ this . githubOTAProgress = 0 ;
246+
247+ this . systemService . startGithubOTA ( fwUrl , wwwUrl ) . subscribe ( {
248+ next : ( ) => {
249+ this . toastrService . info ( 'Update started' , 'GitHub OTA' ) ;
250+ this . startOTAPolling ( ) ;
251+ } ,
252+ error : ( err ) => {
253+ this . isGithubOTA = false ;
254+ const msg = err . error ?. message || err . message || 'Failed to start update' ;
255+ this . toastrService . error ( msg , 'Error' ) ;
256+ }
257+ } ) ;
258+ }
259+
260+ private startOTAPolling ( ) {
261+ this . stopOTAPolling ( ) ;
262+
263+ const poll = ( ) => {
264+ this . otaPollSub = this . systemService . getGithubOTAStatus ( ) . subscribe ( {
265+ next : ( status ) => {
266+ this . githubOTAStep = status . step ;
267+ this . githubOTAProgress = status . progress ;
268+
269+ if ( status . step === 'rebooting' ) {
270+ this . stopOTAPolling ( ) ;
271+ this . toastrService . success ( 'Update complete! Device is rebooting...' , 'Success' ) ;
272+ this . startRebootCheck ( ) ;
273+ return ;
274+ }
275+
276+ if ( status . step === 'error' ) {
277+ this . stopOTAPolling ( ) ;
278+ this . isGithubOTA = false ;
279+ this . toastrService . error ( status . error || 'Update failed' , 'Error' ) ;
280+ return ;
281+ }
282+
283+ if ( status . running ) {
284+ setTimeout ( ( ) => poll ( ) , 1000 ) ;
285+ } else {
286+ this . isGithubOTA = false ;
287+ }
288+ } ,
289+ error : ( ) => {
290+ // Device may have rebooted, start checking
291+ this . stopOTAPolling ( ) ;
292+ this . startRebootCheck ( ) ;
293+ }
294+ } ) ;
295+ } ;
296+
297+ poll ( ) ;
298+ }
299+
300+ private stopOTAPolling ( ) {
301+ if ( this . otaPollSub ) {
302+ this . otaPollSub . unsubscribe ( ) ;
303+ this . otaPollSub = null ;
304+ }
305+ }
306+
307+ private startRebootCheck ( ) {
308+ let attempts = 0 ;
309+ const maxAttempts = 60 ;
310+
311+ const check = ( ) => {
312+ attempts ++ ;
313+ if ( attempts > maxAttempts ) {
314+ this . isGithubOTA = false ;
315+ this . toastrService . warning ( 'Device did not come back online within 60 seconds' , 'Warning' ) ;
316+ return ;
317+ }
318+
319+ this . rebootCheckSub = this . systemService . getInfo ( ) . subscribe ( {
320+ next : ( ) => {
321+ // Device is back!
322+ this . isGithubOTA = false ;
323+ this . toastrService . success ( 'Device is back online! Reloading...' , 'Success' ) ;
324+ setTimeout ( ( ) => window . location . reload ( ) , 1500 ) ;
325+ } ,
326+ error : ( ) => {
327+ // Not back yet, retry
328+ setTimeout ( ( ) => check ( ) , 1000 ) ;
329+ }
330+ } ) ;
331+ } ;
332+
333+ // Wait 5 seconds before first check
334+ setTimeout ( ( ) => check ( ) , 5000 ) ;
335+ }
336+
194337 public restart ( ) {
195338 this . systemService . restart ( ) . subscribe ( res => {
196339
0 commit comments