Merge pull request #4450 from FinnStutzenstein/offlineOperator
offline operator
This commit is contained in:
commit
ad653de915
@ -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 { LoginPrivacyPolicyComponent } from './site/login/components/login-privacy-policy/login-privacy-policy.component';
|
||||||
import { ResetPasswordComponent } from './site/login/components/reset-password/reset-password.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 { ResetPasswordConfirmComponent } from './site/login/components/reset-password-confirm/reset-password-confirm.component';
|
||||||
import { AppPreloader } from './shared/utils/app-preloader';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global app routing
|
* Global app routing
|
||||||
@ -30,8 +29,7 @@ const routes: Routes = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes, { preloadingStrategy: AppPreloader })],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule]
|
||||||
providers: [AppPreloader]
|
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {}
|
||||||
|
@ -42,8 +42,7 @@ export class AuthService {
|
|||||||
/**
|
/**
|
||||||
* Try to log in a user.
|
* Try to log in a user.
|
||||||
*
|
*
|
||||||
* Returns an observable 'user' with the correct login information or an error.
|
* Returns an observable with the correct login information or an error.
|
||||||
* The user will then be stored in the {@link OperatorService},
|
|
||||||
* errors will be forwarded to the parents error function.
|
* errors will be forwarded to the parents error function.
|
||||||
*
|
*
|
||||||
* @param username
|
* @param username
|
||||||
@ -56,18 +55,17 @@ export class AuthService {
|
|||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
const response = await this.http.post<LoginResponse>(environment.urlPrefix + '/users/login/', user);
|
const response = await this.http.post<LoginResponse>(environment.urlPrefix + '/users/login/', user);
|
||||||
this.operator.user = new User(response.user);
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout function for both the client and the server.
|
* Logout function for both the client and the server.
|
||||||
*
|
*
|
||||||
* Will clear the current {@link OperatorService} and
|
* Will clear the current operator and datastore and
|
||||||
* send a `post`-request to `/apps/users/logout/'`
|
* send a `post`-request to `/apps/users/logout/'`. Restarts OpenSlides.
|
||||||
*/
|
*/
|
||||||
public async logout(): Promise<void> {
|
public async logout(): Promise<void> {
|
||||||
this.operator.user = null;
|
await this.operator.setUser(null);
|
||||||
try {
|
try {
|
||||||
await this.http.post(environment.urlPrefix + '/users/logout/', {});
|
await this.http.post(environment.urlPrefix + '/users/logout/', {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
import { DataStoreService } from './data-store.service';
|
|
||||||
import { WhoAmIResponse } from './operator.service';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This service handles everything connected with being offline.
|
* 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.
|
* Sets the offline flag. Restores the DataStoreService to the last known configuration.
|
||||||
*/
|
*/
|
||||||
public async goOfflineBecauseFailedWhoAmI(): Promise<void> {
|
public goOfflineBecauseFailedWhoAmI(): void {
|
||||||
this._offline = true;
|
this._offline = true;
|
||||||
console.log('offline because whoami failed.');
|
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 {
|
public goOnline(): void {
|
||||||
this._offline = false;
|
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ import { Injectable } from '@angular/core';
|
|||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
import { WebsocketService } from './websocket.service';
|
import { WebsocketService } from './websocket.service';
|
||||||
import { OperatorService } from './operator.service';
|
import { OperatorService, WhoAmIResponse } from './operator.service';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { AutoupdateService } from './autoupdate.service';
|
import { AutoupdateService } from './autoupdate.service';
|
||||||
import { DataStoreService } from './data-store.service';
|
import { DataStoreService } from './data-store.service';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the bootup/showdown of this application.
|
* Handles the bootup/showdown of this application.
|
||||||
@ -51,8 +52,11 @@ export class OpenSlidesService {
|
|||||||
*/
|
*/
|
||||||
public async bootup(): Promise<void> {
|
public async bootup(): Promise<void> {
|
||||||
// start autoupdate if the user is logged in:
|
// start autoupdate if the user is logged in:
|
||||||
const response = await this.operator.whoAmI();
|
const response = await this.operator.whoAmIFromStorage();
|
||||||
this.operator.guestsEnabled = response.guest_enabled;
|
await this.bootupWithWhoAmI(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async bootupWithWhoAmI(response: WhoAmIResponse): Promise<void> {
|
||||||
if (!response.user && !response.guest_enabled) {
|
if (!response.user && !response.guest_enabled) {
|
||||||
this.redirectUrl = location.pathname;
|
this.redirectUrl = location.pathname;
|
||||||
|
|
||||||
@ -66,8 +70,14 @@ export class OpenSlidesService {
|
|||||||
// Goto login, if the user isn't login and guests are not allowed
|
// Goto login, if the user isn't login and guests are not allowed
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkOperator(false);
|
||||||
} else {
|
} else {
|
||||||
await this.afterLoginBootup(response.user_id);
|
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
|
* @param userId
|
||||||
*/
|
*/
|
||||||
public async afterLoginBootup(userId: number): Promise<void> {
|
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');
|
const lastUserId = await this.storageService.get<number>('lastUserLoggedIn');
|
||||||
// if the user changed, reset the cache and save the new user.
|
// if the user changed, reset the cache and save the new user.
|
||||||
if (userId !== lastUserId) {
|
if (userId !== lastUserId) {
|
||||||
@ -96,33 +106,42 @@ export class OpenSlidesService {
|
|||||||
if (changeId > 0) {
|
if (changeId > 0) {
|
||||||
changeId += 1;
|
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.
|
this.websocketService.connect({ changeId: changeId }); // Request changes after changeId.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shuts OpenSlides down. The websocket is closed and the operator is not set.
|
* 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.websocketService.close();
|
||||||
this.operator.user = null;
|
await this.operator.setUser(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shutdown and bootup.
|
* Shutdown and bootup.
|
||||||
*/
|
*/
|
||||||
public async reboot(): Promise<void> {
|
public async reboot(): Promise<void> {
|
||||||
this.shutdown();
|
await this.shutdown();
|
||||||
await this.bootup();
|
await this.bootup();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify that the operator is the same as it was before. Should be alled on a reconnect.
|
* 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();
|
const response = await this.operator.whoAmI();
|
||||||
// User logged off.
|
// User logged off.
|
||||||
if (!response.user && !response.guest_enabled) {
|
if (!response.user && !response.guest_enabled) {
|
||||||
this.shutdown();
|
await this.shutdown();
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
@ -130,8 +149,9 @@ export class OpenSlidesService {
|
|||||||
(!this.operator.user && response.user_id)
|
(!this.operator.user && response.user_id)
|
||||||
) {
|
) {
|
||||||
// user changed
|
// user changed
|
||||||
|
await this.DS.clear();
|
||||||
await this.reboot();
|
await this.reboot();
|
||||||
} else {
|
} else if (requestChanges) {
|
||||||
// User is still the same, but check for missed autoupdates.
|
// User is still the same, but check for missed autoupdates.
|
||||||
this.autoupdateService.requestChanges();
|
this.autoupdateService.requestChanges();
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
|
|
||||||
import { Observable, BehaviorSubject } from 'rxjs';
|
import { Observable, BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
@ -11,6 +10,9 @@ import { OfflineService } from './offline.service';
|
|||||||
import { ViewUser } from 'app/site/users/models/view-user';
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
|
import { OnAfterAppsLoaded } from '../onAfterAppsLoaded';
|
||||||
import { UserRepositoryService } from '../repositories/users/user-repository.service';
|
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
|
* 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;
|
export type Permission = string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response format of the WHoAMI request.
|
* Response format of the WhoAmI request.
|
||||||
*/
|
*/
|
||||||
export interface WhoAmIResponse {
|
export interface WhoAmIResponse {
|
||||||
user_id: number;
|
user_id: number;
|
||||||
@ -27,6 +29,16 @@ export interface WhoAmIResponse {
|
|||||||
user: User;
|
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.
|
* The operator represents the user who is using OpenSlides.
|
||||||
*
|
*
|
||||||
@ -42,6 +54,10 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
*/
|
*/
|
||||||
private _user: User;
|
private _user: User;
|
||||||
|
|
||||||
|
public get user(): User {
|
||||||
|
return this._user;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The operator as a view user. We need a separation here, because
|
* The operator as a view user. We need a separation here, because
|
||||||
* we need to acces the operators permissions, before we get data
|
* we need to acces the operators permissions, before we get data
|
||||||
@ -49,13 +65,6 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
*/
|
*/
|
||||||
private _viewUser: ViewUser;
|
private _viewUser: ViewUser;
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the user that corresponds to operator.
|
|
||||||
*/
|
|
||||||
public get user(): User {
|
|
||||||
return this._user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the user that corresponds to operator.
|
* Get the user that corresponds to operator.
|
||||||
*/
|
*/
|
||||||
@ -63,23 +72,16 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
return this._viewUser;
|
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 {
|
public get isAnonymous(): boolean {
|
||||||
return !this.user || this.user.id === 0;
|
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}.
|
* 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`.
|
* 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,
|
* Sets up an observer for watching changes in the DS. If the operator user or groups are changed,
|
||||||
* the operator's permissions are updated.
|
* the operator's permissions are updated.
|
||||||
*
|
*
|
||||||
* @param http HttpClient
|
* @param http
|
||||||
* @param DS
|
* @param DS
|
||||||
* @param offlineService
|
* @param offlineService
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
private http: HttpClient,
|
private http: HttpService,
|
||||||
private DS: DataStoreService,
|
private DS: DataStoreService,
|
||||||
private offlineService: OfflineService,
|
private offlineService: OfflineService,
|
||||||
private userRepository: UserRepositoryService
|
private collectionStringMapper: CollectionStringMapperService,
|
||||||
|
private storageService: StorageService
|
||||||
) {
|
) {
|
||||||
this.DS.changeObservable.subscribe(newModel => {
|
this.DS.changeObservable.subscribe(newModel => {
|
||||||
if (this._user) {
|
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.
|
* Load the repo to get a view user.
|
||||||
*/
|
*/
|
||||||
public onAfterAppsLoaded(): void {
|
public onAfterAppsLoaded(): void {
|
||||||
this.userRepoLoaded = true;
|
this.userRepository = this.collectionStringMapper.getRepository(ViewUser) as UserRepositoryService;
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
this._viewUser = this.userRepository.getViewModel(this.user.id);
|
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.
|
* Updates the user and update the permissions.
|
||||||
*
|
*
|
||||||
* @param user The user to set.
|
* @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;
|
this._user = user;
|
||||||
if (user && this.userRepoLoaded) {
|
if (saveToStorage) {
|
||||||
|
await this.saveUserToStorate();
|
||||||
|
}
|
||||||
|
if (user && this.userRepository) {
|
||||||
this._viewUser = this.userRepository.getViewModel(user.id);
|
this._viewUser = this.userRepository.getViewModel(user.id);
|
||||||
} else {
|
} else {
|
||||||
this._viewUser = null;
|
this._viewUser = null;
|
||||||
@ -158,33 +202,68 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls `/apps/users/whoami` to find out the real operator.
|
* Calls `/apps/users/whoami` to find out the real operator.
|
||||||
|
*
|
||||||
* @returns The response of the WhoAmI request.
|
* @returns The response of the WhoAmI request.
|
||||||
*/
|
*/
|
||||||
public async whoAmI(): Promise<WhoAmIResponse> {
|
public async whoAmI(): Promise<WhoAmIResponse> {
|
||||||
try {
|
try {
|
||||||
const response = await this.http.get<WhoAmIResponse>(environment.urlPrefix + '/users/whoami/').toPromise();
|
const response = await this.http.get(environment.urlPrefix + '/users/whoami/');
|
||||||
if (response && response.user) {
|
if (isWhoAmIResponse(response)) {
|
||||||
this.user = new User(response.user);
|
this.processWhoAmIResponse(response);
|
||||||
|
await this.storageService.set(WHOAMI_STORAGE_KEY, response);
|
||||||
|
this.currentWhoAmIResponse = response;
|
||||||
|
} else {
|
||||||
|
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
||||||
}
|
}
|
||||||
return response;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement the offline service. Currently a guest-whoami response is returned and
|
|
||||||
// the DS cleared.
|
|
||||||
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
this.offlineService.goOfflineBecauseFailedWhoAmI();
|
||||||
return this.offlineService.getLastWhoAmI();
|
|
||||||
}
|
}
|
||||||
|
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
|
* @param response The WhoAMI response
|
||||||
* the operator
|
*/
|
||||||
|
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> {
|
public getUserObservable(): Observable<User> {
|
||||||
return this.operatorSubject.asObservable();
|
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> {
|
public getViewUserObservable(): Observable<ViewUser> {
|
||||||
return this.viewOperatorSubject.asObservable();
|
return this.viewOperatorSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@ import { environment } from 'environments/environment.prod';
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class ServertimeService {
|
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 FAILURE_TIMEOUT = 30;
|
||||||
private static NORMAL_TIMEOUT = 60 * 5;
|
private static NORMAL_TIMEOUT = 60 * 5;
|
||||||
|
|
||||||
|
@ -46,10 +46,6 @@ export class WebsocketService {
|
|||||||
*/
|
*/
|
||||||
private _connectEvent: EventEmitter<void> = new EventEmitter<void>();
|
private _connectEvent: EventEmitter<void> = new EventEmitter<void>();
|
||||||
|
|
||||||
private connectionOpen = false;
|
|
||||||
|
|
||||||
private sendQueueWhileNotConnected: string[] = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Getter for the connect event.
|
* Getter for the connect event.
|
||||||
*/
|
*/
|
||||||
@ -57,6 +53,32 @@ export class WebsocketService {
|
|||||||
return this._connectEvent;
|
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.
|
* The websocket.
|
||||||
*/
|
*/
|
||||||
@ -134,7 +156,7 @@ export class WebsocketService {
|
|||||||
this._reconnectEvent.emit();
|
this._reconnectEvent.emit();
|
||||||
}
|
}
|
||||||
this._connectEvent.emit();
|
this._connectEvent.emit();
|
||||||
this.connectionOpen = true;
|
this._connectionOpen = true;
|
||||||
this.sendQueueWhileNotConnected.forEach(entry => {
|
this.sendQueueWhileNotConnected.forEach(entry => {
|
||||||
this.websocket.send(entry);
|
this.websocket.send(entry);
|
||||||
});
|
});
|
||||||
@ -160,8 +182,9 @@ export class WebsocketService {
|
|||||||
this.websocket.onclose = (event: CloseEvent) => {
|
this.websocket.onclose = (event: CloseEvent) => {
|
||||||
this.zone.run(() => {
|
this.zone.run(() => {
|
||||||
this.websocket = null;
|
this.websocket = null;
|
||||||
this.connectionOpen = false;
|
this._connectionOpen = false;
|
||||||
if (event.code !== 1000) {
|
if (event.code !== 1000) {
|
||||||
|
console.error(event);
|
||||||
// Do not show the message snackbar on the projector
|
// Do not show the message snackbar on the projector
|
||||||
// tests for /projector and /projector/<id>
|
// tests for /projector and /projector/<id>
|
||||||
const onProjector = this.router.url.match(/^\/projector(\/[0-9]+\/?)?$/);
|
const onProjector = this.router.url.match(/^\/projector(\/[0-9]+\/?)?$/);
|
||||||
@ -182,6 +205,7 @@ export class WebsocketService {
|
|||||||
this.connect({ enableAutoupdates: true });
|
this.connect({ enableAutoupdates: true });
|
||||||
}, timeout);
|
}, timeout);
|
||||||
}
|
}
|
||||||
|
this._closeEvent.emit();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -235,7 +259,7 @@ export class WebsocketService {
|
|||||||
|
|
||||||
// Either send directly or add to queue, if not connected.
|
// Either send directly or add to queue, if not connected.
|
||||||
const jsonMessage = JSON.stringify(message);
|
const jsonMessage = JSON.stringify(message);
|
||||||
if (this.connectionOpen) {
|
if (this.isConnected) {
|
||||||
this.websocket.send(jsonMessage);
|
this.websocket.send(jsonMessage);
|
||||||
} else {
|
} else {
|
||||||
this.sendQueueWhileNotConnected.push(jsonMessage);
|
this.sendQueueWhileNotConnected.push(jsonMessage);
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
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
|
* List view for the global settings
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Component, forwardRef } from '@angular/core';
|
import { Component, forwardRef } from '@angular/core';
|
||||||
|
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
||||||
|
|
||||||
import { CustomTranslation, CustomTranslations } from 'app/core/translate/translation-parser';
|
import { CustomTranslation, CustomTranslations } from 'app/core/translate/translation-parser';
|
||||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom translations as custom form component
|
* Custom translations as custom form component
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { Subject } from 'rxjs';
|
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
|
||||||
import { History } from 'app/shared/models/core/history';
|
import { History } from 'app/shared/models/core/history';
|
||||||
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';
|
import { HistoryRepositoryService } from 'app/core/repositories/history/history-repository.service';
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
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 { BaseComponent } from 'app/base.component';
|
||||||
import { AuthService } from 'app/core/core-services/auth.service';
|
import { AuthService } from 'app/core/core-services/auth.service';
|
||||||
import { OperatorService } from 'app/core/core-services/operator.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 { environment } from 'environments/environment';
|
||||||
import { OpenSlidesService } from 'app/core/core-services/openslides.service';
|
import { OpenSlidesService } from 'app/core/core-services/openslides.service';
|
||||||
import { LoginDataService } from 'app/core/ui-services/login-data.service';
|
import { LoginDataService } from 'app/core/ui-services/login-data.service';
|
||||||
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
||||||
import { HttpService } from 'app/core/core-services/http.service';
|
import { HttpService } from 'app/core/core-services/http.service';
|
||||||
|
import { User } from 'app/shared/models/users/user';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login mask component.
|
* Login mask component.
|
||||||
@ -22,7 +25,7 @@ import { HttpService } from 'app/core/core-services/http.service';
|
|||||||
templateUrl: './login-mask.component.html',
|
templateUrl: './login-mask.component.html',
|
||||||
styleUrls: ['./login-mask.component.scss']
|
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
|
* Show or hide password and change the indicator accordingly
|
||||||
*/
|
*/
|
||||||
@ -53,6 +56,8 @@ export class LoginMaskComponent extends BaseComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
public inProcess = false;
|
public inProcess = false;
|
||||||
|
|
||||||
|
public operatorSubscription: Subscription | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for the login component
|
* 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.loginErrorMsg = '';
|
||||||
this.inProcess = true;
|
this.inProcess = true;
|
||||||
try {
|
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.inProcess = false;
|
||||||
this.OpenSlides.afterLoginBootup(res.user_id);
|
await this.operator.setUser(new User(response.user));
|
||||||
let redirect = this.OpenSlides.redirectUrl ? this.OpenSlides.redirectUrl : '/';
|
this.OpenSlides.afterLoginBootup(response.user_id);
|
||||||
if (redirect.includes('login')) {
|
this.redirectUser();
|
||||||
redirect = '/';
|
|
||||||
}
|
|
||||||
this.router.navigate([redirect]);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.loginForm.setErrors({
|
this.loginForm.setErrors({
|
||||||
notFound: true
|
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
|
* Go to the reset password view
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { BaseComponent } from '../../../../base.component';
|
import { BaseComponent } from '../../../../base.component';
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
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 { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { BaseComponent } from '../../../../base.component';
|
import { BaseComponent } from '../../../../base.component';
|
||||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { HttpService } from 'app/core/core-services/http.service';
|
||||||
import { MatSnackBar } from '@angular/material';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset password component.
|
* Reset password component.
|
||||||
@ -40,7 +41,7 @@ export class ResetPasswordConfirmComponent extends BaseComponent implements OnIn
|
|||||||
public constructor(
|
public constructor(
|
||||||
protected titleService: Title,
|
protected titleService: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
private http: HttpClient,
|
private http: HttpService,
|
||||||
formBuilder: FormBuilder,
|
formBuilder: FormBuilder,
|
||||||
private activatedRoute: ActivatedRoute,
|
private activatedRoute: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@ -88,13 +89,11 @@ export class ResetPasswordConfirmComponent extends BaseComponent implements OnIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.http
|
await this.http.post<void>(environment.urlPrefix + '/users/reset-password-confirm/', {
|
||||||
.post<void>(environment.urlPrefix + '/users/reset-password-confirm/', {
|
user_id: this.user_id,
|
||||||
user_id: this.user_id,
|
token: this.token,
|
||||||
token: this.token,
|
password: this.newPasswordForm.get('password').value
|
||||||
password: this.newPasswordForm.get('password').value
|
});
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
// TODO: Does we get a response for displaying?
|
// TODO: Does we get a response for displaying?
|
||||||
this.matSnackBar.open(
|
this.matSnackBar.open(
|
||||||
this.translate.instant('Your password was resetted successfully!'),
|
this.translate.instant('Your password was resetted successfully!'),
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Title } from '@angular/platform-browser';
|
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 { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { environment } from 'environments/environment';
|
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar } from '@angular/material';
|
||||||
import { Router } from '@angular/router';
|
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.
|
* Reset password component.
|
||||||
*
|
*
|
||||||
@ -30,7 +31,7 @@ export class ResetPasswordComponent extends BaseComponent implements OnInit {
|
|||||||
public constructor(
|
public constructor(
|
||||||
protected titleService: Title,
|
protected titleService: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
private http: HttpClient,
|
private http: HttpService,
|
||||||
formBuilder: FormBuilder,
|
formBuilder: FormBuilder,
|
||||||
private matSnackBar: MatSnackBar,
|
private matSnackBar: MatSnackBar,
|
||||||
private router: Router
|
private router: Router
|
||||||
@ -57,11 +58,9 @@ export class ResetPasswordComponent extends BaseComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.http
|
await this.http.post<void>(environment.urlPrefix + '/users/reset-password/', {
|
||||||
.post<void>(environment.urlPrefix + '/users/reset-password/', {
|
email: this.resetPasswordForm.get('email').value
|
||||||
email: this.resetPasswordForm.get('email').value
|
});
|
||||||
})
|
|
||||||
.toPromise();
|
|
||||||
// TODO: Does we get a response for displaying?
|
// TODO: Does we get a response for displaying?
|
||||||
this.matSnackBar.open(
|
this.matSnackBar.open(
|
||||||
this.translate.instant('An email with a password reset link was send!'),
|
this.translate.instant('An email with a password reset link was send!'),
|
||||||
|
@ -16,53 +16,43 @@ const routes: Routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
loadChildren: './common/os-common.module#OsCommonModule',
|
loadChildren: './common/os-common.module#OsCommonModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'agenda',
|
path: 'agenda',
|
||||||
loadChildren: './agenda/agenda.module#AgendaModule',
|
loadChildren: './agenda/agenda.module#AgendaModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'assignments',
|
path: 'assignments',
|
||||||
loadChildren: './assignments/assignments.module#AssignmentsModule',
|
loadChildren: './assignments/assignments.module#AssignmentsModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'mediafiles',
|
path: 'mediafiles',
|
||||||
loadChildren: './mediafiles/mediafiles.module#MediafilesModule',
|
loadChildren: './mediafiles/mediafiles.module#MediafilesModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'motions',
|
path: 'motions',
|
||||||
loadChildren: './motions/motions.module#MotionsModule',
|
loadChildren: './motions/motions.module#MotionsModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'settings',
|
path: 'settings',
|
||||||
loadChildren: './config/config.module#ConfigModule',
|
loadChildren: './config/config.module#ConfigModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'users',
|
path: 'users',
|
||||||
loadChildren: './users/users.module#UsersModule',
|
loadChildren: './users/users.module#UsersModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags',
|
path: 'tags',
|
||||||
loadChildren: './tags/tag.module#TagModule',
|
loadChildren: './tags/tag.module#TagModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'history',
|
path: 'history',
|
||||||
loadChildren: './history/history.module#HistoryModule',
|
loadChildren: './history/history.module#HistoryModule'
|
||||||
data: { preload: true }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'projectors',
|
path: 'projectors',
|
||||||
loadChildren: './projector/projector.module#ProjectorModule',
|
loadChildren: './projector/projector.module#ProjectorModule'
|
||||||
data: { preload: true }
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
canActivateChild: [AuthGuard]
|
canActivateChild: [AuthGuard]
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
<span> {{ getLangName(this.translate.currentLang) }} </span>
|
<span> {{ getLangName(this.translate.currentLang) }} </span>
|
||||||
</a>
|
</a>
|
||||||
<div *ngIf="isLoggedIn">
|
<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>
|
<mat-icon>person</mat-icon>
|
||||||
<span translate>Show profile</span>
|
<span translate>Show profile</span>
|
||||||
</a>
|
</a>
|
||||||
|
@ -99,7 +99,9 @@ export class SiteComponent extends BaseComponent implements OnInit {
|
|||||||
// Scroll to top if accessing a page, not via browser history stack
|
// Scroll to top if accessing a page, not via browser history stack
|
||||||
if (event instanceof NavigationEnd) {
|
if (event instanceof NavigationEnd) {
|
||||||
const contentContainer = document.querySelector('.mat-sidenav-content');
|
const contentContainer = document.querySelector('.mat-sidenav-content');
|
||||||
contentContainer.scrollTo(0, 0);
|
if (contentContainer) {
|
||||||
|
contentContainer.scrollTo(0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user