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 { 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 {}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
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 response;
|
||||
} catch (e) {
|
||||
// TODO: Implement the offline service. Currently a guest-whoami response is returned and
|
||||
// the DS cleared.
|
||||
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
|
||||
* 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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 { 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
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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';
|
||||
|
@ -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/', {
|
||||
user_id: this.user_id,
|
||||
token: this.token,
|
||||
password: this.newPasswordForm.get('password').value
|
||||
})
|
||||
.toPromise();
|
||||
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
|
||||
});
|
||||
// TODO: Does we get a response for displaying?
|
||||
this.matSnackBar.open(
|
||||
this.translate.instant('Your password was resetted successfully!'),
|
||||
|
@ -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/', {
|
||||
email: this.resetPasswordForm.get('email').value
|
||||
})
|
||||
.toPromise();
|
||||
await this.http.post<void>(environment.urlPrefix + '/users/reset-password/', {
|
||||
email: this.resetPasswordForm.get('email').value
|
||||
});
|
||||
// TODO: Does we get a response for displaying?
|
||||
this.matSnackBar.open(
|
||||
this.translate.instant('An email with a password reset link was send!'),
|
||||
|
@ -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]
|
||||
|
@ -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>
|
||||
|
@ -99,7 +99,9 @@ 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');
|
||||
contentContainer.scrollTo(0, 0);
|
||||
if (contentContainer) {
|
||||
contentContainer.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user