Merge pull request #4450 from FinnStutzenstein/offlineOperator

offline operator
This commit is contained in:
Emanuel Schütze 2019-03-06 09:24:52 +01:00 committed by GitHub
commit ad653de915
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 280 additions and 173 deletions

View File

@ -7,7 +7,6 @@ import { LoginLegalNoticeComponent } from './site/login/components/login-legal-n
import { LoginPrivacyPolicyComponent } from './site/login/components/login-privacy-policy/login-privacy-policy.component';
import { ResetPasswordComponent } from './site/login/components/reset-password/reset-password.component';
import { ResetPasswordConfirmComponent } from './site/login/components/reset-password-confirm/reset-password-confirm.component';
import { AppPreloader } from './shared/utils/app-preloader';
/**
* Global app routing
@ -30,8 +29,7 @@ const routes: Routes = [
];
@NgModule({
imports: [RouterModule.forRoot(routes, { preloadingStrategy: AppPreloader })],
exports: [RouterModule],
providers: [AppPreloader]
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {}

View File

@ -42,8 +42,7 @@ export class AuthService {
/**
* Try to log in a user.
*
* Returns an observable 'user' with the correct login information or an error.
* The user will then be stored in the {@link OperatorService},
* Returns an observable with the correct login information or an error.
* errors will be forwarded to the parents error function.
*
* @param username
@ -56,18 +55,17 @@ export class AuthService {
password: password
};
const response = await this.http.post<LoginResponse>(environment.urlPrefix + '/users/login/', user);
this.operator.user = new User(response.user);
return response;
}
/**
* Logout function for both the client and the server.
*
* Will clear the current {@link OperatorService} and
* send a `post`-request to `/apps/users/logout/'`
* Will clear the current operator and datastore and
* send a `post`-request to `/apps/users/logout/'`. Restarts OpenSlides.
*/
public async logout(): Promise<void> {
this.operator.user = null;
await this.operator.setUser(null);
try {
await this.http.post(environment.urlPrefix + '/users/logout/', {});
} catch (e) {

View File

@ -1,8 +1,5 @@
import { Injectable } from '@angular/core';
import { DataStoreService } from './data-store.service';
import { WhoAmIResponse } from './operator.service';
/**
* This service handles everything connected with being offline.
*
@ -20,20 +17,15 @@ export class OfflineService {
}
/**
* Constructor to create the AutoupdateService. Calls the constructor of the parent class.
* @param DS
*/
public constructor(private DS: DataStoreService) {}
public constructor() {}
/**
* Sets the offline flag. Restores the DataStoreService to the last known configuration.
*/
public async goOfflineBecauseFailedWhoAmI(): Promise<void> {
public goOfflineBecauseFailedWhoAmI(): void {
this._offline = true;
console.log('offline because whoami failed.');
// TODO: Init the DS from cache.
await this.DS.clear();
}
/**
@ -50,16 +42,4 @@ export class OfflineService {
public goOnline(): void {
this._offline = false;
}
/**
* Returns the last cached WhoAmI response.
*/
public getLastWhoAmI(): WhoAmIResponse {
// TODO: use a cached WhoAmI response.
return {
user_id: null,
guest_enabled: false,
user: null
};
}
}

View File

@ -2,10 +2,11 @@ import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { WebsocketService } from './websocket.service';
import { OperatorService } from './operator.service';
import { OperatorService, WhoAmIResponse } from './operator.service';
import { StorageService } from './storage.service';
import { AutoupdateService } from './autoupdate.service';
import { DataStoreService } from './data-store.service';
import { take } from 'rxjs/operators';
/**
* Handles the bootup/showdown of this application.
@ -51,8 +52,11 @@ export class OpenSlidesService {
*/
public async bootup(): Promise<void> {
// start autoupdate if the user is logged in:
const response = await this.operator.whoAmI();
this.operator.guestsEnabled = response.guest_enabled;
const response = await this.operator.whoAmIFromStorage();
await this.bootupWithWhoAmI(response);
}
private async bootupWithWhoAmI(response: WhoAmIResponse): Promise<void> {
if (!response.user && !response.guest_enabled) {
this.redirectUrl = location.pathname;
@ -66,8 +70,14 @@ export class OpenSlidesService {
// Goto login, if the user isn't login and guests are not allowed
this.router.navigate(['/login']);
}
this.checkOperator(false);
} else {
await this.afterLoginBootup(response.user_id);
// Check for the operator via a async whoami (so no await here)
// to validate, that the cache was correct.
this.checkOperator(false);
}
}
@ -78,7 +88,7 @@ export class OpenSlidesService {
* @param userId
*/
public async afterLoginBootup(userId: number): Promise<void> {
// Else, check, which user was logged in last time
// Check, which user was logged in last time
const lastUserId = await this.storageService.get<number>('lastUserLoggedIn');
// if the user changed, reset the cache and save the new user.
if (userId !== lastUserId) {
@ -96,33 +106,42 @@ export class OpenSlidesService {
if (changeId > 0) {
changeId += 1;
}
// disconnect the WS connection, if there was one. This is needed
// to update the connection parameters, namely the cookies. If the user
// is changed, the WS needs to reconnect, so the new connection holds the new
// user information.
if (this.websocketService.isConnected) {
this.websocketService.close();
// Wait for the disconnect.
await this.websocketService.closeEvent.pipe(take(1)).toPromise();
}
this.websocketService.connect({ changeId: changeId }); // Request changes after changeId.
}
/**
* Shuts OpenSlides down. The websocket is closed and the operator is not set.
*/
public shutdown(): void {
public async shutdown(): Promise<void> {
this.websocketService.close();
this.operator.user = null;
await this.operator.setUser(null);
}
/**
* Shutdown and bootup.
*/
public async reboot(): Promise<void> {
this.shutdown();
await this.shutdown();
await this.bootup();
}
/**
* Verify that the operator is the same as it was before. Should be alled on a reconnect.
*/
private async checkOperator(): Promise<void> {
private async checkOperator(requestChanges: boolean = true): Promise<void> {
const response = await this.operator.whoAmI();
// User logged off.
if (!response.user && !response.guest_enabled) {
this.shutdown();
await this.shutdown();
this.router.navigate(['/login']);
} else {
if (
@ -130,8 +149,9 @@ export class OpenSlidesService {
(!this.operator.user && response.user_id)
) {
// user changed
await this.DS.clear();
await this.reboot();
} else {
} else if (requestChanges) {
// User is still the same, but check for missed autoupdates.
this.autoupdateService.requestChanges();
}

View File

@ -1,5 +1,4 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
@ -11,6 +10,9 @@ import { OfflineService } from './offline.service';
import { ViewUser } from 'app/site/users/models/view-user';
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
import { UserRepositoryService } from '../repositories/users/user-repository.service';
import { CollectionStringMapperService } from './collectionStringMapper.service';
import { StorageService } from './storage.service';
import { HttpService } from './http.service';
/**
* Permissions on the client are just strings. This makes clear, that
@ -19,7 +21,7 @@ import { UserRepositoryService } from '../repositories/users/user-repository.ser
export type Permission = string;
/**
* Response format of the WHoAMI request.
* Response format of the WhoAmI request.
*/
export interface WhoAmIResponse {
user_id: number;
@ -27,6 +29,16 @@ export interface WhoAmIResponse {
user: User;
}
function isWhoAmIResponse(obj: any): obj is WhoAmIResponse {
if (!obj) {
return false;
}
const whoAmI = obj as WhoAmIResponse;
return whoAmI.guest_enabled !== undefined && whoAmI.user !== undefined && whoAmI.user_id !== undefined;
}
const WHOAMI_STORAGE_KEY = 'whoami';
/**
* The operator represents the user who is using OpenSlides.
*
@ -42,6 +54,10 @@ export class OperatorService implements OnAfterAppsLoaded {
*/
private _user: User;
public get user(): User {
return this._user;
}
/**
* The operator as a view user. We need a separation here, because
* we need to acces the operators permissions, before we get data
@ -49,13 +65,6 @@ export class OperatorService implements OnAfterAppsLoaded {
*/
private _viewUser: ViewUser;
/**
* Get the user that corresponds to operator.
*/
public get user(): User {
return this._user;
}
/**
* Get the user that corresponds to operator.
*/
@ -63,23 +72,16 @@ export class OperatorService implements OnAfterAppsLoaded {
return this._viewUser;
}
/**
* Sets the current operator.
*
* The permissions are updated and the new user published.
*/
public set user(user: User) {
this.updateUser(user);
}
public get isAnonymous(): boolean {
return !this.user || this.user.id === 0;
}
/**
* Save, if quests are enabled.
* Save, if guests are enabled.
*/
public guestsEnabled: boolean;
public get guestsEnabled(): boolean {
return this.currentWhoAmIResponse ? this.currentWhoAmIResponse.guest_enabled : false;
}
/**
* The permissions of the operator. Updated via {@method updatePermissions}.
@ -99,21 +101,27 @@ export class OperatorService implements OnAfterAppsLoaded {
/**
* Do not access the repo before it wasn't loaded. Will be true after `onAfterAppsLoaded`.
*/
private userRepoLoaded = false;
private userRepository: UserRepositoryService | null;
/**
* The current WhoAmI response to extract the user (the operator) from.
*/
private currentWhoAmIResponse: WhoAmIResponse | null;
/**
* Sets up an observer for watching changes in the DS. If the operator user or groups are changed,
* the operator's permissions are updated.
*
* @param http HttpClient
* @param http
* @param DS
* @param offlineService
*/
public constructor(
private http: HttpClient,
private http: HttpService,
private DS: DataStoreService,
private offlineService: OfflineService,
private userRepository: UserRepositoryService
private collectionStringMapper: CollectionStringMapperService,
private storageService: StorageService
) {
this.DS.changeObservable.subscribe(newModel => {
if (this._user) {
@ -131,24 +139,60 @@ export class OperatorService implements OnAfterAppsLoaded {
});
}
/**
* Gets the current WHoAmI response from the storage.
*/
public async whoAmIFromStorage(): Promise<WhoAmIResponse> {
const defaultResponse = {
user_id: null,
guest_enabled: false,
user: null
};
let response: WhoAmIResponse;
try {
response = await this.storageService.get<WhoAmIResponse>(WHOAMI_STORAGE_KEY);
if (response) {
this.processWhoAmIResponse(response);
} else {
response = defaultResponse;
}
} catch (e) {
response = defaultResponse;
}
this.currentWhoAmIResponse = response;
return this.currentWhoAmIResponse;
}
/**
* Load the repo to get a view user.
*/
public onAfterAppsLoaded(): void {
this.userRepoLoaded = true;
this.userRepository = this.collectionStringMapper.getRepository(ViewUser) as UserRepositoryService;
if (this.user) {
this._viewUser = this.userRepository.getViewModel(this.user.id);
}
}
/**
* Sets the operator user. Will be saved to storage
* @param user The new operator.
*/
public async setUser(user: User): Promise<void> {
await this.updateUser(user, true);
}
/**
* Updates the user and update the permissions.
*
* @param user The user to set.
* @param saveToStoare Whether to save the user to the storage WhoAmI.
*/
private updateUser(user: User | null): void {
private async updateUser(user: User | null, saveToStorage: boolean = false): Promise<void> {
this._user = user;
if (user && this.userRepoLoaded) {
if (saveToStorage) {
await this.saveUserToStorate();
}
if (user && this.userRepository) {
this._viewUser = this.userRepository.getViewModel(user.id);
} else {
this._viewUser = null;
@ -158,33 +202,68 @@ export class OperatorService implements OnAfterAppsLoaded {
/**
* Calls `/apps/users/whoami` to find out the real operator.
*
* @returns The response of the WhoAmI request.
*/
public async whoAmI(): Promise<WhoAmIResponse> {
try {
const response = await this.http.get<WhoAmIResponse>(environment.urlPrefix + '/users/whoami/').toPromise();
if (response && response.user) {
this.user = new User(response.user);
}
return response;
} catch (e) {
// TODO: Implement the offline service. Currently a guest-whoami response is returned and
// the DS cleared.
const response = await this.http.get(environment.urlPrefix + '/users/whoami/');
if (isWhoAmIResponse(response)) {
this.processWhoAmIResponse(response);
await this.storageService.set(WHOAMI_STORAGE_KEY, response);
this.currentWhoAmIResponse = response;
} else {
this.offlineService.goOfflineBecauseFailedWhoAmI();
return this.offlineService.getLastWhoAmI();
}
} catch (e) {
this.offlineService.goOfflineBecauseFailedWhoAmI();
}
return this.currentWhoAmIResponse;
}
/**
* Returns the operatorSubject as an observable.
* Saves the user to storage by wrapping it into a (maybe existing)
* WhoAMI response.
*/
private async saveUserToStorate(): Promise<void> {
if (!this.currentWhoAmIResponse) {
this.currentWhoAmIResponse = {
user_id: null,
guest_enabled: false,
user: null
};
}
if (this.user) {
this.currentWhoAmIResponse.user_id = this.user.id;
this.currentWhoAmIResponse.user = this.user;
} else {
this.currentWhoAmIResponse.user_id = null;
this.currentWhoAmIResponse.user = null;
}
await this.storageService.set(WHOAMI_STORAGE_KEY, this.currentWhoAmIResponse);
}
/**
* Processes a WhoAmI response and set the user appropriately.
*
* Services an components can use it to get informed when something changes in
* the operator
* @param response The WhoAMI response
*/
private processWhoAmIResponse(response: WhoAmIResponse): void {
this.updateUser(response.user ? new User(response.user) : null);
}
/**
* @returns an observable for the operator as a user.
*/
public getUserObservable(): Observable<User> {
return this.operatorSubject.asObservable();
}
/**
* @returns an observable for the operator as a viewUser. Note, that
* the viewUser might not be there, so for reliable (and not display) information,
* use the `getUserObservable`.
*/
public getViewUserObservable(): Observable<ViewUser> {
return this.viewOperatorSubject.asObservable();
}

View File

@ -16,6 +16,8 @@ import { environment } from 'environments/environment.prod';
providedIn: 'root'
})
export class ServertimeService {
// TODO: couple this with the offlineService: Just retry often, if we are online.
// When we are offline, this is not necessary.
private static FAILURE_TIMEOUT = 30;
private static NORMAL_TIMEOUT = 60 * 5;

View File

@ -46,10 +46,6 @@ export class WebsocketService {
*/
private _connectEvent: EventEmitter<void> = new EventEmitter<void>();
private connectionOpen = false;
private sendQueueWhileNotConnected: string[] = [];
/**
* Getter for the connect event.
*/
@ -57,6 +53,32 @@ export class WebsocketService {
return this._connectEvent;
}
/**
* Listeners will be nofitied, if the wesocket connection is closed.
*/
private _closeEvent: EventEmitter<void> = new EventEmitter<void>();
/**
* Getter for the close event.
*/
public get closeEvent(): EventEmitter<void> {
return this._closeEvent;
}
/**
* Saves, if the connection is open
*/
private _connectionOpen = false;
/**
* Whether the WebSocket connection is established
*/
public get isConnected(): boolean {
return this._connectionOpen;
}
private sendQueueWhileNotConnected: string[] = [];
/**
* The websocket.
*/
@ -134,7 +156,7 @@ export class WebsocketService {
this._reconnectEvent.emit();
}
this._connectEvent.emit();
this.connectionOpen = true;
this._connectionOpen = true;
this.sendQueueWhileNotConnected.forEach(entry => {
this.websocket.send(entry);
});
@ -160,8 +182,9 @@ export class WebsocketService {
this.websocket.onclose = (event: CloseEvent) => {
this.zone.run(() => {
this.websocket = null;
this.connectionOpen = false;
this._connectionOpen = false;
if (event.code !== 1000) {
console.error(event);
// Do not show the message snackbar on the projector
// tests for /projector and /projector/<id>
const onProjector = this.router.url.match(/^\/projector(\/[0-9]+\/?)?$/);
@ -182,6 +205,7 @@ export class WebsocketService {
this.connect({ enableAutoupdates: true });
}, timeout);
}
this._closeEvent.emit();
});
};
}
@ -235,7 +259,7 @@ export class WebsocketService {
// Either send directly or add to queue, if not connected.
const jsonMessage = JSON.stringify(message);
if (this.connectionOpen) {
if (this.isConnected) {
this.websocket.send(jsonMessage);
} else {
this.sendQueueWhileNotConnected.push(jsonMessage);

View File

@ -1,27 +0,0 @@
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
/**
* Custom preload strategy class
*
* @example
* ```
* {
* path: 'agenda',
* loadChildren: './agenda/agenda.module#AgendaModule',
* data: { preload: true }
* }
* ```
*/
export class AppPreloader implements PreloadingStrategy {
/**
* Custom preload function.
* Can be add to routes as data argument.
*
* @param route The route to load
* @param load The load function
*/
public preload(route: Route, load: Function): Observable<any> {
return route.data && route.data.preload ? load() : of(null);
}
}

View File

@ -1,8 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { ConfigRepositoryService, ConfigGroup } from '../../../../core/repositories/config/config-repository.service';
import { BaseComponent } from '../../../../base.component';
import { ConfigRepositoryService, ConfigGroup } from 'app/core/repositories/config/config-repository.service';
import { BaseComponent } from 'app/base.component';
/**
* List view for the global settings

View File

@ -1,7 +1,7 @@
import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { CustomTranslation, CustomTranslations } from 'app/core/translate/translation-parser';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
/**
* Custom translations as custom form component

View File

@ -1,10 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { Subject } from 'rxjs';
import { History } from 'app/shared/models/core/history';
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';

View File

@ -1,16 +1,19 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from 'app/base.component';
import { AuthService } from 'app/core/core-services/auth.service';
import { OperatorService } from 'app/core/core-services/operator.service';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { environment } from 'environments/environment';
import { OpenSlidesService } from 'app/core/core-services/openslides.service';
import { LoginDataService } from 'app/core/ui-services/login-data.service';
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
import { HttpService } from 'app/core/core-services/http.service';
import { User } from 'app/shared/models/users/user';
/**
* Login mask component.
@ -22,7 +25,7 @@ import { HttpService } from 'app/core/core-services/http.service';
templateUrl: './login-mask.component.html',
styleUrls: ['./login-mask.component.scss']
})
export class LoginMaskComponent extends BaseComponent implements OnInit {
export class LoginMaskComponent extends BaseComponent implements OnInit, OnDestroy {
/**
* Show or hide password and change the indicator accordingly
*/
@ -53,6 +56,8 @@ export class LoginMaskComponent extends BaseComponent implements OnInit {
*/
public inProcess = false;
public operatorSubscription: Subscription | null;
/**
* Constructor for the login component
*
@ -99,6 +104,32 @@ export class LoginMaskComponent extends BaseComponent implements OnInit {
},
() => {}
);
// Maybe the operator changes and the user is logged in. If so, redirect him and boot OpenSlides.
this.operatorSubscription = this.operator.getUserObservable().subscribe(user => {
if (user) {
this.clearOperatorSubscription();
this.redirectUser();
this.OpenSlides.afterLoginBootup(user.id);
}
});
}
/**
* Clear the subscription on destroy.
*/
public ngOnDestroy(): void {
this.clearOperatorSubscription();
}
/**
* Clears the subscription to the operator.
*/
private clearOperatorSubscription(): void {
if (this.operatorSubscription) {
this.operatorSubscription.unsubscribe();
this.operatorSubscription = null;
}
}
/**
@ -120,14 +151,12 @@ export class LoginMaskComponent extends BaseComponent implements OnInit {
this.loginErrorMsg = '';
this.inProcess = true;
try {
const res = await this.authService.login(this.loginForm.value.username, this.loginForm.value.password);
const response = await this.authService.login(this.loginForm.value.username, this.loginForm.value.password);
this.clearOperatorSubscription(); // We take control, not the subscription.
this.inProcess = false;
this.OpenSlides.afterLoginBootup(res.user_id);
let redirect = this.OpenSlides.redirectUrl ? this.OpenSlides.redirectUrl : '/';
if (redirect.includes('login')) {
redirect = '/';
}
this.router.navigate([redirect]);
await this.operator.setUser(new User(response.user));
this.OpenSlides.afterLoginBootup(response.user_id);
this.redirectUser();
} catch (e) {
this.loginForm.setErrors({
notFound: true
@ -137,6 +166,17 @@ export class LoginMaskComponent extends BaseComponent implements OnInit {
}
}
/**
* Redirects the user to the page where he came from.
*/
private redirectUser(): void {
let redirect = this.OpenSlides.redirectUrl ? this.OpenSlides.redirectUrl : '/';
if (redirect.includes('login')) {
redirect = '/';
}
this.router.navigate([redirect]);
}
/**
* Go to the reset password view
*/

View File

@ -1,5 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component';

View File

@ -1,13 +1,14 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { MatSnackBar } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { ActivatedRoute, Router } from '@angular/router';
import { MatSnackBar } from '@angular/material';
import { HttpService } from 'app/core/core-services/http.service';
/**
* Reset password component.
@ -40,7 +41,7 @@ export class ResetPasswordConfirmComponent extends BaseComponent implements OnIn
public constructor(
protected titleService: Title,
protected translate: TranslateService,
private http: HttpClient,
private http: HttpService,
formBuilder: FormBuilder,
private activatedRoute: ActivatedRoute,
private router: Router,
@ -88,13 +89,11 @@ export class ResetPasswordConfirmComponent extends BaseComponent implements OnIn
}
try {
await this.http
.post<void>(environment.urlPrefix + '/users/reset-password-confirm/', {
await this.http.post<void>(environment.urlPrefix + '/users/reset-password-confirm/', {
user_id: this.user_id,
token: this.token,
password: this.newPasswordForm.get('password').value
})
.toPromise();
});
// TODO: Does we get a response for displaying?
this.matSnackBar.open(
this.translate.instant('Your password was resetted successfully!'),

View File

@ -1,14 +1,15 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { MatSnackBar } from '@angular/material';
import { Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { environment } from 'environments/environment';
import { BaseComponent } from '../../../../base.component';
import { HttpService } from 'app/core/core-services/http.service';
/**
* Reset password component.
*
@ -30,7 +31,7 @@ export class ResetPasswordComponent extends BaseComponent implements OnInit {
public constructor(
protected titleService: Title,
protected translate: TranslateService,
private http: HttpClient,
private http: HttpService,
formBuilder: FormBuilder,
private matSnackBar: MatSnackBar,
private router: Router
@ -57,11 +58,9 @@ export class ResetPasswordComponent extends BaseComponent implements OnInit {
}
try {
await this.http
.post<void>(environment.urlPrefix + '/users/reset-password/', {
await this.http.post<void>(environment.urlPrefix + '/users/reset-password/', {
email: this.resetPasswordForm.get('email').value
})
.toPromise();
});
// TODO: Does we get a response for displaying?
this.matSnackBar.open(
this.translate.instant('An email with a password reset link was send!'),

View File

@ -16,53 +16,43 @@ const routes: Routes = [
children: [
{
path: '',
loadChildren: './common/os-common.module#OsCommonModule',
data: { preload: true }
loadChildren: './common/os-common.module#OsCommonModule'
},
{
path: 'agenda',
loadChildren: './agenda/agenda.module#AgendaModule',
data: { preload: true }
loadChildren: './agenda/agenda.module#AgendaModule'
},
{
path: 'assignments',
loadChildren: './assignments/assignments.module#AssignmentsModule',
data: { preload: true }
loadChildren: './assignments/assignments.module#AssignmentsModule'
},
{
path: 'mediafiles',
loadChildren: './mediafiles/mediafiles.module#MediafilesModule',
data: { preload: true }
loadChildren: './mediafiles/mediafiles.module#MediafilesModule'
},
{
path: 'motions',
loadChildren: './motions/motions.module#MotionsModule',
data: { preload: true }
loadChildren: './motions/motions.module#MotionsModule'
},
{
path: 'settings',
loadChildren: './config/config.module#ConfigModule',
data: { preload: true }
loadChildren: './config/config.module#ConfigModule'
},
{
path: 'users',
loadChildren: './users/users.module#UsersModule',
data: { preload: true }
loadChildren: './users/users.module#UsersModule'
},
{
path: 'tags',
loadChildren: './tags/tag.module#TagModule',
data: { preload: true }
loadChildren: './tags/tag.module#TagModule'
},
{
path: 'history',
loadChildren: './history/history.module#HistoryModule',
data: { preload: true }
loadChildren: './history/history.module#HistoryModule'
},
{
path: 'projectors',
loadChildren: './projector/projector.module#ProjectorModule',
data: { preload: true }
loadChildren: './projector/projector.module#ProjectorModule'
}
],
canActivateChild: [AuthGuard]

View File

@ -36,7 +36,7 @@
<span> {{ getLangName(this.translate.currentLang) }} </span>
</a>
<div *ngIf="isLoggedIn">
<a [routerLink]="['/users/', operator.user.id]" (click)="mobileAutoCloseNav()" mat-list-item>
<a [routerLink]="operator.user ? ['/users/', operator.user.id] : []" (click)="mobileAutoCloseNav()" mat-list-item>
<mat-icon>person</mat-icon>
<span translate>Show profile</span>
</a>

View File

@ -99,8 +99,10 @@ export class SiteComponent extends BaseComponent implements OnInit {
// Scroll to top if accessing a page, not via browser history stack
if (event instanceof NavigationEnd) {
const contentContainer = document.querySelector('.mat-sidenav-content');
if (contentContainer) {
contentContainer.scrollTo(0, 0);
}
}
});
}