diff --git a/static/skywire-manager-src/src/app/app.component.html b/static/skywire-manager-src/src/app/app.component.html index a461d0eea..7c8353e9a 100644 --- a/static/skywire-manager-src/src/app/app.component.html +++ b/static/skywire-manager-src/src/app/app.component.html @@ -7,6 +7,6 @@
- - + +
diff --git a/static/skywire-manager-src/src/app/app.component.ts b/static/skywire-manager-src/src/app/app.component.ts index 1decc85d1..59c344c98 100644 --- a/static/skywire-manager-src/src/app/app.component.ts +++ b/static/skywire-manager-src/src/app/app.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router'; import { MatDialog } from '@angular/material/dialog'; -import { of } from 'rxjs'; +import { of, Subscription } from 'rxjs'; import { delay, flatMap } from 'rxjs/operators'; import { StorageService } from './services/storage.service'; @@ -19,12 +19,20 @@ import { processServiceError } from './utils/errors'; styleUrls: ['./app.component.scss'] }) export class AppComponent { + static currentInstance: AppComponent; + // If the app is showing the VPN client. inVpnClient = false; + // If the app is in the login page. Needed to know if content should be shown even if + // hypervisorPkObtained is false. + inLoginPage = false; // If the pk of the hypervisor has been obtained. hypervisorPkObtained = false; pkErrorShown = false; + pkErrorsFound = 0; + + obtainPkSubscription: Subscription; constructor( // Imported to call its constructor right after opening the app. @@ -35,6 +43,8 @@ export class AppComponent { private languageService: LanguageService, private apiService: ApiService, ) { + AppComponent.currentInstance = this; + // Close the snackbar when opening a modal window. dialog.afterOpened.subscribe(() => snackbarService.closeCurrent()); @@ -53,9 +63,19 @@ export class AppComponent { dialog.afterAllClosed.subscribe(() => snackbarService.closeCurrentIfTemporaryError()); // Check if the app is showing the VPN client. - router.events.subscribe(() => { + router.events.subscribe((e: any) => { this.inVpnClient = router.url.includes('/vpn/') || router.url.includes('vpnlogin'); + // Check if the user enters or leaves the login page. + if (e.url) { + const previousInLoginPageValue = this.inLoginPage; + this.inLoginPage = e.url.includes('login'); + + if (previousInLoginPageValue && !this.inLoginPage && !this.hypervisorPkObtained) { + this.checkHypervisorPk(0); + } + } + // Show the correct document title. if (router.url.length > 2) { if (this.inVpnClient) { @@ -72,11 +92,26 @@ export class AppComponent { this.checkHypervisorPk(0); } + /** + * This should be called a frame before leaving the login page, to avoid race conditions in which the + * automatic event code in the constructor changes the value of inLoginPage to false but Angular still + * loads the content of the new page just before taking that value into account. + */ + processLoginDone() { + this.inLoginPage = false; + if (!this.hypervisorPkObtained) { + this.checkHypervisorPk(0); + } + } + /** * Gets the pk of the hypervisor. After that, it initializes services and allows the app to start working. */ private checkHypervisorPk(delayMs: number) { - of(1).pipe(delay(delayMs), flatMap(() => this.apiService.get('about'))).subscribe(result => { + if (this.obtainPkSubscription) { + this.obtainPkSubscription.unsubscribe(); + } + this.obtainPkSubscription = of(1).pipe(delay(delayMs), flatMap(() => this.apiService.get('about'))).subscribe(result => { if (result.public_key) { this.finishStartup(result.public_key); this.hypervisorPkObtained = true; @@ -88,12 +123,17 @@ export class AppComponent { this.checkHypervisorPk(1000); } }, err => { - if (!this.pkErrorShown) { + this.pkErrorsFound += 1; + + if (this.pkErrorsFound > 4 && !this.pkErrorShown) { const e = processServiceError(err); this.snackbarService.showError('start.loading-error', null, true, e); this.pkErrorShown = true; } - this.checkHypervisorPk(1000); + + if (!this.inLoginPage) { + this.checkHypervisorPk(1000); + } }); } diff --git a/static/skywire-manager-src/src/app/components/pages/login/login.component.ts b/static/skywire-manager-src/src/app/components/pages/login/login.component.ts index 5d604931b..4fc45381d 100644 --- a/static/skywire-manager-src/src/app/components/pages/login/login.component.ts +++ b/static/skywire-manager-src/src/app/components/pages/login/login.component.ts @@ -10,6 +10,7 @@ import { SnackbarService } from '../../../services/snackbar.service'; import { InitialSetupComponent } from './initial-setup/initial-setup.component'; import { OperationError } from '../../../utils/operation-error'; import { processServiceError } from '../../../utils/errors'; +import { AppComponent } from 'src/app/app.component'; /** * Login page. @@ -46,8 +47,12 @@ export class LoginComponent implements OnInit, OnDestroy { // Check if the user is already logged. this.verificationSubscription = this.authService.checkLogin().subscribe(response => { if (response !== AuthStates.NotLogged) { - const destination = !this.isForVpn ? ['nodes'] : ['vpn', this.vpnKey, 'status']; - this.router.navigate(destination, { replaceUrl: true }); + // Inform about the redirect a frame before it is done, to avoid problems. + AppComponent.currentInstance.processLoginDone(); + setTimeout(() => { + const destination = !this.isForVpn ? ['nodes'] : ['vpn', this.vpnKey, 'status']; + this.router.navigate(destination, { replaceUrl: true }); + }); } }); }); @@ -83,8 +88,12 @@ export class LoginComponent implements OnInit, OnDestroy { } private onLoginSuccess() { - const destination = !this.isForVpn ? ['nodes'] : ['vpn', this.vpnKey, 'status']; - this.router.navigate(destination, { replaceUrl: true }); + // Inform about the redirect a frame before it is done, to avoid problems. + AppComponent.currentInstance.processLoginDone(); + setTimeout(() => { + const destination = !this.isForVpn ? ['nodes'] : ['vpn', this.vpnKey, 'status']; + this.router.navigate(destination, { replaceUrl: true }); + }); } private onLoginError(err: OperationError) {