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

NAS-130956 / 25.04 / Reduce numbers of layout shifts on login page #10612

Merged
merged 16 commits into from
Sep 16, 2024
Merged
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
:host {
align-items: center;
display: flex;
padding: 32px 32px 0;
padding: 28px 16px 10px;
}

.fake-icon {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<p class="failover-status-message">{{ statusMessage() | translate }}</p>
@if (!disabledReasons()) {
<ngx-skeleton-loader></ngx-skeleton-loader>
}
@if (areReasonsShown()) {
<div class="failover-reasons">
@for (reason of disabledReasons(); track reason) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
:host {
display: block;
margin-top: 10px;
text-align: center;
}

p {
margin-bottom: 0.4rem;
margin-top: 0.4rem;
margin: 0.4rem;
}

ngx-skeleton-loader {
display: inline-block;
flex: 0;
height: 20px;
width: 80%;

&::ng-deep {
span.loader {
height: 22px;
margin: 0;
}
}
}
6 changes: 3 additions & 3 deletions src/app/pages/signin/signin-form/signin-form.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
type="button"
color="primary"
ixTest="log-in"
[disabled]="isLoading$ | async"
[disabled]="isFormDisabled()"
(click)="login()"
>
{{ 'Log In' | translate }}
Expand Down Expand Up @@ -58,7 +58,7 @@
type="button"
color="primary"
ixTest="log-in"
[disabled]="isLoading$ | async"
[disabled]="isFormDisabled()"
(click)="loginWithOtp()"
>
{{ 'Proceed' | translate }}
Expand All @@ -68,7 +68,7 @@
mat-button
type="button"
ixTest="otp-log-in"
[disabled]="isLoading$ | async"
[disabled]="isFormDisabled()"
(click)="cancelOtpLogin()"
>{{ 'Cancel' | translate }}</button>
</div>
Expand Down
10 changes: 7 additions & 3 deletions src/app/pages/signin/signin-form/signin-form.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { AsyncPipe } from '@angular/common';
import {
ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnInit,
ChangeDetectionStrategy, ChangeDetectorRef, Component, computed, Inject, input, OnInit,
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import {
FormBuilder, Validators, FormsModule, ReactiveFormsModule,
} from '@angular/forms';
Expand Down Expand Up @@ -44,6 +45,8 @@ import { WebSocketService } from 'app/services/ws.service';
],
})
export class SigninFormComponent implements OnInit {
disabled = input.required<boolean>();

hasTwoFactor = false;
showSecurityWarning = false;

Expand All @@ -57,7 +60,8 @@ export class SigninFormComponent implements OnInit {
otp: ['', Validators.required],
});

protected isLoading$ = this.signinStore.isLoading$;
protected isLoading = toSignal(this.signinStore.isLoading$);
readonly isFormDisabled = computed(() => this.disabled() || this.isLoading());

constructor(
private formBuilder: FormBuilder,
Expand All @@ -73,7 +77,7 @@ export class SigninFormComponent implements OnInit {
this.showSecurityWarning = true;
}

this.isLoading$.pipe(
this.signinStore.isLoading$.pipe(
filter((isLoading) => !isLoading),
take(1),
untilDestroyed(this),
Expand Down
47 changes: 28 additions & 19 deletions src/app/pages/signin/signin.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,37 @@
class="session-progress"
[mode]="(hasLoadingIndicator$ | async) ? 'indeterminate' : 'determinate'"
></mat-progress-bar>
<mat-card>
<mat-card-content>
@if (isConnected$ | async) {

@if (!(isConnected$ | async) && (isConnectedDelayed$ | async) !== null) {
<mat-card>
<mat-card-content>
<ix-disconnected-message
[hasFailover]="hasFailover$ | async"
></ix-disconnected-message>
</mat-card-content>
</mat-card>
} @else if (hasAuthToken && (isTokenWithinTimeline$ | async)) {
<mat-card>
<mat-card-content>
<h3 class="logging-in">{{ 'Logging in...' | translate }}</h3>
</mat-card-content>
</mat-card>
} @else {
<mat-card>
<mat-card-content>
<div>
<div class="logo-wrapper">
<ix-icon name="ix:logo_truenas_scale_full" class="logo"></ix-icon>
</div>

<div class="card-bottom">
@if (canLogin$ | async) {
<div class="form-container">
@if (wasAdminSet$ | async) {
<ix-signin-form></ix-signin-form>
} @else {
<ix-set-admin-password-form></ix-set-admin-password-form>
}
</div>
}
<div class="form-container">
@if (wasAdminSet$ | async) {
<ix-signin-form [disabled]="!(canLogin$ | async)"></ix-signin-form>
} @else {
<ix-set-admin-password-form></ix-set-admin-password-form>
}
</div>

@if (failover$ | async; as failover) {
@if (hasFailover$ | async) {
Expand All @@ -46,12 +59,8 @@
<ix-copyright-line class="copyright" [withIxLogo]="false"></ix-copyright-line>
</div>
</div>
} @else {
<ix-disconnected-message
[hasFailover]="hasFailover$ | async"
></ix-disconnected-message>
}
</mat-card-content>
</mat-card>
</mat-card-content>
</mat-card>
}
</div>
</div>
16 changes: 12 additions & 4 deletions src/app/pages/signin/signin.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
}

.card-bottom {
overflow: auto;
padding: 0 16px;
}

Expand All @@ -58,19 +59,20 @@
}

.form-container {
margin-bottom: 20px;
padding: 20px 32px 8px;
}

.logo-wrapper {
background-color: var(--bg2);

// Fallback for browsers that don't support image-set
background-image: url('assets/images/signin/stars-sky-800w.jpg');
background-image: image-set(url('assets/images/signin/stars-sky-400w.avif') 1x,
url('assets/images/signin/stars-sky-800w.avif') 2x,
url('assets/images/signin/stars-sky-1200w.avif') 3x);
background-size: cover;
border-bottom: solid 1px var(--lines);
height: 250px;

padding: 20% 0;
position: relative;
Expand Down Expand Up @@ -122,11 +124,17 @@
}

.logo-wrapper {
align-items: center;
display: flex;
justify-content: center;
padding: 20% 0;
}
}

.failover-status {
margin-top: 25px;
.logging-in {
box-sizing: border-box;
color: var(--fg2);
padding: 49px 16px 37px;
text-align: center;
width: 100%;
}

28 changes: 25 additions & 3 deletions src/app/pages/signin/signin.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import { MatCard, MatCardContent } from '@angular/material/card';
import { MatFormField } from '@angular/material/form-field';
import { MatInput } from '@angular/material/input';
import { MatProgressBar } from '@angular/material/progress-bar';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { combineLatest } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { combineLatest, Observable, of } from 'rxjs';
import {
delay,
filter, map, switchMap, take,
} from 'rxjs/operators';
import { WINDOW } from 'app/helpers/window.helper';
Expand All @@ -23,6 +26,8 @@ import { SetAdminPasswordFormComponent } from 'app/pages/signin/set-admin-passwo
import { SigninFormComponent } from 'app/pages/signin/signin-form/signin-form.component';
import { SigninStore } from 'app/pages/signin/store/signin.store';
import { TrueCommandStatusComponent } from 'app/pages/signin/true-command-status/true-command-status.component';
import { AuthService } from 'app/services/auth/auth.service';
import { TokenLastUsedService } from 'app/services/token-last-used.service';
import { WebSocketConnectionService } from 'app/services/websocket-connection.service';

@UntilDestroy()
Expand All @@ -35,6 +40,7 @@ import { WebSocketConnectionService } from 'app/services/websocket-connection.se
imports: [
MatFormField,
MatInput,
MatProgressSpinner,
TestIdModule,
MatProgressBar,
MatCard,
Expand All @@ -46,24 +52,40 @@ import { WebSocketConnectionService } from 'app/services/websocket-connection.se
TrueCommandStatusComponent,
DisconnectedMessageComponent,
AsyncPipe,
TranslateModule,
CopyrightLineComponent,
],
providers: [SigninStore],
})
export class SigninComponent implements OnInit {
protected hasAuthToken = this.authService.hasAuthToken;
protected isTokenWithinTimeline$ = this.tokenLastUsedService.isTokenWithinTimeline$;

readonly wasAdminSet$ = this.signinStore.wasAdminSet$;
readonly failover$ = this.signinStore.failover$;
readonly hasFailover$ = this.signinStore.hasFailover$;
readonly canLogin$ = this.signinStore.canLogin$;
readonly isConnected$ = this.wsManager.isConnected$;
readonly hasLoadingIndicator$ = combineLatest([this.signinStore.isLoading$, this.isConnected$]).pipe(
map(([isLoading, isConnected]) => isLoading || !isConnected),
readonly isConnectedDelayed$: Observable<boolean> = of(null).pipe(
delay(2000),
switchMap(() => this.isConnected$),
);
readonly hasLoadingIndicator$ = combineLatest([
this.signinStore.isLoading$,
this.isConnected$,
this.isTokenWithinTimeline$,
]).pipe(
map(([isLoading, isConnected, isTokenWithinTimeline]) => {
return isLoading || !isConnected || (isTokenWithinTimeline && this.hasAuthToken);
}),
);

constructor(
private wsManager: WebSocketConnectionService,
private signinStore: SigninStore,
private dialog: DialogService,
private authService: AuthService,
private tokenLastUsedService: TokenLastUsedService,
@Inject(WINDOW) private window: Window,
) {}

Expand Down
12 changes: 5 additions & 7 deletions src/app/pages/signin/store/signin.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
combineLatest, EMPTY, forkJoin, Observable, of, Subscription, from,
} from 'rxjs';
import {
catchError, distinctUntilChanged, filter, map, switchMap, tap,
catchError, distinctUntilChanged, filter, map, switchMap, take, tap,
} from 'rxjs/operators';
import { FailoverDisabledReason } from 'app/enums/failover-disabled-reason.enum';
import { FailoverStatus } from 'app/enums/failover-status.enum';
Expand All @@ -20,7 +20,7 @@ import { DialogService } from 'app/modules/dialog/dialog.service';
import { AuthService } from 'app/services/auth/auth.service';
import { ErrorHandlerService } from 'app/services/error-handler.service';
import { SystemGeneralService } from 'app/services/system-general.service';
import { TokenLifetimeService } from 'app/services/token-lifetime.service';
import { TokenLastUsedService } from 'app/services/token-last-used.service';
import { UpdateService } from 'app/services/update.service';
import { WebSocketConnectionService } from 'app/services/websocket-connection.service';
import { WebSocketService } from 'app/services/ws.service';
Expand Down Expand Up @@ -69,7 +69,7 @@ export class SigninStore extends ComponentStore<SigninState> {
constructor(
private ws: WebSocketService,
private translate: TranslateService,
private tokenLifetimeService: TokenLifetimeService,
private tokenLastUsedService: TokenLastUsedService,
private dialogService: DialogService,
private systemGeneralService: SystemGeneralService,
private router: Router,
Expand Down Expand Up @@ -238,10 +238,8 @@ export class SigninStore extends ComponentStore<SigninState> {
}

private handleLoginWithToken(): Observable<LoginResult> {
return of(null).pipe(
filter(() => {
const isTokenWithinTimeline = this.tokenLifetimeService.isTokenWithinTimeline;

return this.tokenLastUsedService.isTokenWithinTimeline$.pipe(take(1)).pipe(
filter((isTokenWithinTimeline) => {
if (!isTokenWithinTimeline) {
this.authService.clearAuthToken();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
align-items: center;
display: flex;
justify-content: center;
padding: 10px 0;
text-align: center;

img {
Expand Down
10 changes: 7 additions & 3 deletions src/app/services/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
import { IncomingWebSocketMessage, ResultMessage } from 'app/interfaces/api-message.interface';
import { LoggedInUser } from 'app/interfaces/ds-cache.interface';
import { GlobalTwoFactorConfig } from 'app/interfaces/two-factor-config.interface';
import { TokenLastUsedService } from 'app/services/token-last-used.service';
import { WebSocketConnectionService } from 'app/services/websocket-connection.service';
import { WebSocketService } from 'app/services/ws.service';
import { AppsState } from 'app/store';
Expand All @@ -53,6 +54,10 @@ export class AuthService {
return this.latestTokenGenerated$.asObservable().pipe(filter((token) => !!token));
}

get hasAuthToken(): boolean {
return this.token && this.token !== 'null';
}

private isLoggedIn$ = new BehaviorSubject<boolean>(false);

private generateTokenSubscription: Subscription;
Expand Down Expand Up @@ -87,12 +92,11 @@ export class AuthService {
private wsManager: WebSocketConnectionService,
private store$: Store<AppsState>,
private ws: WebSocketService,
private tokenLastUsedService: TokenLastUsedService,
@Inject(WINDOW) private window: Window,
) {
this.setupAuthenticationUpdate();

this.setupWsConnectionUpdate();

this.setupTokenUpdate();
}

Expand All @@ -118,7 +122,7 @@ export class AuthService {
*/
clearAuthToken(): void {
this.window.sessionStorage.removeItem('loginBannerDismissed');
this.window.localStorage.removeItem('tokenLastUsed');
this.tokenLastUsedService.clearTokenLastUsed();
this.latestTokenGenerated$.next(null);
this.latestTokenGenerated$.complete();
this.latestTokenGenerated$ = new ReplaySubject<string>(1);
Expand Down
Loading
Loading