Merge pull request #3864 from tsiegleauq/restructure_pp_ln

Routing for Privacy Policy and Legal Notice + Restructure Login Components
This commit is contained in:
Finn Stutzenstein 2018-09-10 11:46:22 +02:00 committed by GitHub
commit 44b287248c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 696 additions and 176 deletions

View File

@ -1,4 +1,8 @@
{ {
"/apps/core/version": {
"target": "http://localhost:8000",
"secure": false
},
"/apps/users/login": { "/apps/users/login": {
"target": "http://localhost:8000", "target": "http://localhost:8000",
"secure": false "secure": false

View File

@ -1,12 +1,25 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './site/login/login.component';
import { LoginComponent } from './site/login/components/login-wrapper/login.component';
import { LoginMaskComponent } from './site/login/components/login-mask/login-mask.component';
import { LoginLegalNoticeComponent } from './site/login/components/login-legal-notice/login-legal-notice.component';
import { LoginPrivacyPolicyComponent } from './site/login/components/login-privacy-policy/login-privacy-policy.component';
/** /**
* Global app routing * Global app routing
*/ */
const routes: Routes = [ const routes: Routes = [
{ path: 'login', component: LoginComponent }, {
path: 'login',
component: LoginComponent,
children: [
{ path: '', component: LoginMaskComponent },
{ path: 'legalnotice', component: LoginLegalNoticeComponent },
{ path: 'privacypolicy', component: LoginPrivacyPolicyComponent }
]
},
{ path: 'projector', loadChildren: './projector-container/projector-container.module#ProjectorContainerModule' }, { path: 'projector', loadChildren: './projector-container/projector-container.module#ProjectorContainerModule' },
{ path: '', loadChildren: './site/site.module#SiteModule' }, { path: '', loadChildren: './site/site.module#SiteModule' },
{ path: '**', redirectTo: '' } { path: '**', redirectTo: '' }

View File

@ -5,6 +5,8 @@ import { Subject } from 'rxjs';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
import { OpenSlidesComponent } from './openslides.component'; import { OpenSlidesComponent } from './openslides.component';
import { OpenSlidesService } from './core/services/openslides.service'; import { OpenSlidesService } from './core/services/openslides.service';
import { LoginDataService } from './core/services/login-data.service';
import { ConfigService } from './core/services/config.service';
/** /**
* Angular's global App Component * Angular's global App Component
@ -38,7 +40,9 @@ export class AppComponent {
public constructor( public constructor(
translate: TranslateService, translate: TranslateService,
private operator: OperatorService, private operator: OperatorService,
private OpenSlides: OpenSlidesService private OpenSlides: OpenSlidesService,
private configService: ConfigService,
private loginDataService: LoginDataService
) { ) {
// manually add the supported languages // manually add the supported languages
translate.addLangs(['en', 'de', 'fr']); translate.addLangs(['en', 'de', 'fr']);
@ -61,6 +65,8 @@ export class AppComponent {
// Setup the operator after the root injector is known. // Setup the operator after the root injector is known.
this.operator.setupSubscription(); this.operator.setupSubscription();
this.configService.setupSubscription();
this.loginDataService.setupSubscription();
this.OpenSlides.bootup(); // Yeah! this.OpenSlides.bootup(); // Yeah!
} }

View File

@ -33,7 +33,9 @@ export class ConfigService extends OpenSlidesComponent {
*/ */
public constructor() { public constructor() {
super(); super();
}
public setupSubscription(): void {
this.DS.changeObservable.subscribe(data => { this.DS.changeObservable.subscribe(data => {
// on changes notify the observers for specific keys. // on changes notify the observers for specific keys.
if (data instanceof Config && this.configSubjects[data.key]) { if (data instanceof Config && this.configSubjects[data.key]) {

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { LoginDataService } from './login-data.service';
describe('LoginDataService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [LoginDataService]
});
});
it('should be created', inject([LoginDataService], (service: LoginDataService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,76 @@
import { Injectable } from '@angular/core';
import { OpenSlidesComponent } from 'app/openslides.component';
import { ConfigService } from './config.service';
import { BehaviorSubject, Observable } from 'rxjs';
/**
* This service holds the privacy policy and the legal notice, so they are available
* even if the user is not logged in.
*/
@Injectable({
providedIn: 'root'
})
export class LoginDataService extends OpenSlidesComponent {
/**
* Holds the privacy policy
*/
private _privacy_policy = new BehaviorSubject<string>('');
/**
* Returns an observable for the privacy policy
*/
public get privacy_policy(): Observable<string> {
return this._privacy_policy.asObservable();
}
/**
* Holds the legal notice
*/
private _legal_notice = new BehaviorSubject<string>('');
/**
* Returns an observable for the legal notice
*/
public get legal_notice(): Observable<string> {
return this._legal_notice.asObservable();
}
/**
* Constructs this service. The config service is needed to update the privacy
* policy and legal notice, when their config values change.
* @param configService
*/
public constructor(private configService: ConfigService) {
super();
}
/**
* Should be called, when the data store is set up. Updates the values when the
* corresponding config variables change.
*/
public setupSubscription(): void {
this.configService.get('general_event_privacy_policy').subscribe(value => {
this.setPrivacyPolicy(value);
});
this.configService.get('general_event_legal_notice').subscribe(value => {
this.setLegalNotice(value);
});
}
/**
* Setter for the privacy policy
* @param privacyPolicy The new privacy policy to set
*/
public setPrivacyPolicy(privacyPolicy: string): void {
this._privacy_policy.next(privacyPolicy);
}
/**
* Setter for the legal notice
* @param legalNotice The new legal notice to set
*/
public setLegalNotice(legalNotice: string): void {
this._legal_notice.next(legalNotice);
}
}

View File

@ -1,9 +1,9 @@
<mat-toolbar color='primary' class="footer"> <mat-toolbar color='primary' class="footer">
<mat-toolbar-row> <mat-toolbar-row>
<button mat-button class="footer-link" [routerLink]="['/legalnotice']"> <button mat-button class="footer-link" [routerLink]=legalNoticeUrl>
<span translate>Legal Notice</span> <span translate>Legal Notice</span>
</button> </button>
<button mat-button class="footer-link" [routerLink]="['/privacypolicy']"> <button mat-button class="footer-link" [routerLink]=privacyPolicyUrl>
<span translate>Privacy Policy</span> <span translate>Privacy Policy</span>
</button> </button>
<span class="footer-right">© <span translate>Copyright by</span>&#160;<a href='https://openslides.org/'>OpenSlides</a> <span class="footer-right">© <span translate>Copyright by</span>&#160;<a href='https://openslides.org/'>OpenSlides</a>

View File

@ -1,4 +1,5 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
/** /**
* Reusable footer Apps. * Reusable footer Apps.
@ -18,12 +19,29 @@ import { Component, OnInit } from '@angular/core';
}) })
export class FooterComponent implements OnInit { export class FooterComponent implements OnInit {
/** /**
* Empty constructor * Indicates to location of the legal notice
*/ */
public constructor() {} public legalNoticeUrl = '/legalnotice';
/** /**
* empty onInit * Indicated the location of the privacy policy
*/ */
public ngOnInit(): void {} public privacyPolicyUrl = '/privacypolicy';
/**
* Empty constructor
*/
public constructor(private route: ActivatedRoute) {}
/**
* If on login page, redirect the legal notice and privacy policy not to /URL
* but to /login/URL
*/
public ngOnInit(): void {
if (this.route.snapshot.url[0] && this.route.snapshot.url[0].path === 'login') {
this.legalNoticeUrl = '/login/legalnotice';
this.privacyPolicyUrl = '/login/privacypolicy';
}
}
} }

View File

@ -0,0 +1,24 @@
<mat-card class="os-card on-transition-fade">
<div>
<div class='legal-notice-text' [innerHtml]='legalNotice'></div>
<mat-divider></mat-divider>
<div *ngIf="versionInfo" class='version-text'>
<a [attr.href]="versionInfo.openslides_url" target="_blank">
OpenSlides {{ versionInfo.openslides_version }}
</a>
(<span translate>License</span>: {{ versionInfo.openslides_license }})
<div *ngIf="versionInfo.plugins.length">
<div translate>Installed plugins</div>:
<div *ngFor="let plugin of versionInfo.plugins">
<a [attr.href]="plugin.url" target="_blank">
{{ plugin.verbose_name }} {{ plugin.version }}
</a>
<div *ngIf="plugin.license">
(<span translate>License</span>: {{ plugin.license }})
</div>
</div>
</div>
</div>
</div>
</mat-card>

View File

@ -0,0 +1,9 @@
.legal-notice-text {
display: block;
padding-bottom: 20px;
}
.version-text {
display: block;
padding-top: 20px;
}

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LegalNoticeContentComponent } from './legal-notice-content.component';
describe('LegalNoticeComponent', () => {
let component: LegalNoticeContentComponent;
let fixture: ComponentFixture<LegalNoticeContentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LegalNoticeContentComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LegalNoticeContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,106 @@
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { LoginDataService } from '../../../core/services/login-data.service';
import { environment } from 'environments/environment';
import { HttpClient } from '@angular/common/http';
/**
* Characterize a plugin. This data is retrieved from the server
*/
interface PluginDescription {
/**
* The name of the plugin
*/
verbose_name: string;
/**
* the version
*/
version: string;
/**
* The url to the main webpage of the plugin
*/
url: string;
/**
* The license
*/
license: string;
}
/**
* Represents metadata about the current installation.
*/
interface VersionResponse {
/**
* The lience string. Like 'MIT', 'GPLv2', ...
*/
openslides_license: string;
/**
* The URl to the main webpage of OpenSlides.
*/
openslides_url: string;
/**
* The current version.
*/
openslides_version: string;
/**
* A list of installed plugins.
*/
plugins: PluginDescription[];
}
/**
* Shared component to hold the content of the Legal Notice.
* Used in login and site container.
*/
@Component({
selector: 'os-legal-notice-content',
templateUrl: './legal-notice-content.component.html',
styleUrls: ['./legal-notice-content.component.scss']
})
export class LegalNoticeContentComponent implements OnInit {
/**
* The legal notive text for the ui.
*/
public legalNotice: string;
/**
* Holds the version info retrieved from the server for the ui.
*/
public versionInfo: VersionResponse;
/**
* Imports the LoginDataService, the translations and and HTTP Service
* @param loginDataService
* @param translate
* @param http
*/
public constructor(
private loginDataService: LoginDataService,
private translate: TranslateService,
private http: HttpClient
) {}
/**
* Subscribes for the legal notice text.
*/
public ngOnInit(): void {
this.loginDataService.legal_notice.subscribe(legalNotice => {
if (legalNotice) {
this.legalNotice = this.translate.instant(legalNotice);
}
});
// Query the version info.
this.http
.get<VersionResponse>(environment.urlPrefix + '/core/version/', {})
.subscribe((info: VersionResponse) => {
this.versionInfo = info;
});
}
}

View File

@ -0,0 +1,6 @@
<mat-card class='os-card on-transition-fade'>
<div *ngIf='privacyPolicy' [innerHtml]='privacyPolicy'></div>
<div *ngIf='!privacyPolicy' translate>
The event manager hasn't set up a privacy policy yet.
</div>
</mat-card>

View File

@ -0,0 +1,3 @@
mat-card {
height: 100%;
}

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PrivacyPolicyContentComponent } from './privacy-policy-content.component';
describe('PrivacyPolicyComponent', () => {
let component: PrivacyPolicyContentComponent;
let fixture: ComponentFixture<PrivacyPolicyContentComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [PrivacyPolicyContentComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PrivacyPolicyContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,37 @@
import { Component, OnInit } from '@angular/core';
import { LoginDataService } from '../../../core/services/login-data.service';
import { TranslateService } from '@ngx-translate/core';
/**
* Shared component to hold the content of the Privacy Policy.
* Used in login and site container.
*/
@Component({
selector: 'os-privacy-policy-content',
templateUrl: './privacy-policy-content.component.html',
styleUrls: ['./privacy-policy-content.component.scss']
})
export class PrivacyPolicyContentComponent implements OnInit {
/**
* The actual privacy policy as string
*/
public privacyPolicy: string;
/**
* Imports the loginDataService and the translation service
* @param loginDataService Login Data
* @param translate for the translation
*/
public constructor(private loginDataService: LoginDataService, private translate: TranslateService) {}
/**
* Subscribes for the privacy policy text
*/
public ngOnInit(): void {
this.loginDataService.privacy_policy.subscribe(privacyPolicy => {
if (privacyPolicy) {
this.privacyPolicy = this.translate.instant(privacyPolicy);
}
});
}
}

View File

@ -1,4 +1,5 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -14,7 +15,8 @@ import {
MatSnackBarModule, MatSnackBarModule,
MatTableModule, MatTableModule,
MatPaginatorModule, MatPaginatorModule,
MatSortModule MatSortModule,
MatTabsModule
} from '@angular/material'; } from '@angular/material';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { MatListModule } from '@angular/material/list'; import { MatListModule } from '@angular/material/list';
@ -36,8 +38,8 @@ import { PermsDirective } from './directives/perms.directive';
import { DomChangeDirective } from './directives/dom-change.directive'; import { DomChangeDirective } from './directives/dom-change.directive';
import { HeadBarComponent } from './components/head-bar/head-bar.component'; import { HeadBarComponent } from './components/head-bar/head-bar.component';
import { FooterComponent } from './components/footer/footer.component'; import { FooterComponent } from './components/footer/footer.component';
import { LegalNoticeContentComponent } from './components/legal-notice-content/legal-notice-content.component';
import { RouterModule } from '@angular/router'; import { PrivacyPolicyContentComponent } from './components/privacy-policy-content/privacy-policy-content.component';
library.add(fas); library.add(fas);
@ -70,10 +72,10 @@ library.add(fas);
MatListModule, MatListModule,
MatExpansionModule, MatExpansionModule,
MatMenuModule, MatMenuModule,
MatSnackBarModule,
MatDialogModule, MatDialogModule,
TranslateModule.forChild(), MatSnackBarModule,
FontAwesomeModule, FontAwesomeModule,
TranslateModule.forChild(),
RouterModule RouterModule
], ],
exports: [ exports: [
@ -96,13 +98,23 @@ library.add(fas);
MatMenuModule, MatMenuModule,
MatDialogModule, MatDialogModule,
MatSnackBarModule, MatSnackBarModule,
MatTabsModule,
FontAwesomeModule, FontAwesomeModule,
TranslateModule, TranslateModule,
PermsDirective, PermsDirective,
DomChangeDirective, DomChangeDirective,
FooterComponent, FooterComponent,
HeadBarComponent HeadBarComponent,
LegalNoticeContentComponent,
PrivacyPolicyContentComponent
], ],
declarations: [PermsDirective, DomChangeDirective, HeadBarComponent, FooterComponent] declarations: [
PermsDirective,
DomChangeDirective,
HeadBarComponent,
FooterComponent,
LegalNoticeContentComponent,
PrivacyPolicyContentComponent
]
}) })
export class SharedModule {} export class SharedModule {}

View File

@ -1,13 +1,3 @@
<mat-toolbar color='primary'> <os-head-bar appName="Legal Notice"></os-head-bar>
<span class='app-name on-transition-fade' translate>Legal Notice</span>
</mat-toolbar>
<mat-card class="os-card on-transition-fade"> <os-legal-notice-content></os-legal-notice-content>
<div>
<div class='legal-notice-text' [innerHtml]='legalNotice'></div>
<mat-divider></mat-divider>
<div class='version-text'>
OpenSlides 3.0 PRE ALPHA (Lizenz: MIT)
</div>
</div>
</mat-card>

View File

@ -1,9 +0,0 @@
.legal-notice-text {
display: block;
padding-bottom: 20px;
}
.version-text {
display: block;
padding-top: 20px;
}

View File

@ -1,6 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ConfigService } from '../../core/services/config.service';
import { TranslateService } from '@ngx-translate/core';
@Component({ @Component({
selector: 'os-legal-notice', selector: 'os-legal-notice',
@ -8,15 +6,7 @@ import { TranslateService } from '@ngx-translate/core';
styleUrls: ['./legal-notice.component.scss'] styleUrls: ['./legal-notice.component.scss']
}) })
export class LegalNoticeComponent implements OnInit { export class LegalNoticeComponent implements OnInit {
public legalNotice: string; public constructor() {}
public constructor(private configService: ConfigService, private translate: TranslateService) {} public ngOnInit(): void {}
public ngOnInit(): void {
this.configService.get('general_event_legal_notice').subscribe(value => {
if (value) {
this.legalNotice = this.translate.instant(value);
}
});
}
} }

View File

@ -0,0 +1,15 @@
.title-center {
margin: 0 auto;
}
mat-toolbar {
text-align: center;
display: grid;
}
button {
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 25px;
}

View File

@ -0,0 +1,9 @@
<main>
<mat-toolbar color="primary" translate>
Legal Notice
</mat-toolbar>
<os-legal-notice-content></os-legal-notice-content>
<button mat-raised-button color="accent" routerLink="/login" routerLinkActive="router-link-active" translate>Login</button>
</main>

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginLegalNoticeComponent } from './login-legal-notice.component';
describe('LoginLegalNoticeComponent', () => {
let component: LoginLegalNoticeComponent;
let fixture: ComponentFixture<LoginLegalNoticeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LoginLegalNoticeComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginLegalNoticeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,22 @@
import { Component, OnInit } from '@angular/core';
/**
* Container to display the legal notice on the login page.
* Uses the corresponding shared component
*/
@Component({
selector: 'os-login-legal-notice',
templateUrl: './login-legal-notice.component.html',
styleUrls: ['./login-legal-notice.component.scss', '../../assets/login-info-pages.scss']
})
export class LoginLegalNoticeComponent implements OnInit {
/**
* Empty constructor
*/
public constructor() {}
/**
* Empty onInit
*/
public ngOnInit(): void {}
}

View File

@ -0,0 +1,26 @@
<div class="form-wrapper">
<mat-spinner *ngIf="inProcess"></mat-spinner>
<form [formGroup]="loginForm" class="login-form" (ngSubmit)="formLogin()">
<mat-form-field>
<input matInput required placeholder="User name" formControlName="username" [errorStateMatcher]="parentErrorStateMatcher">
</mat-form-field>
<br>
<mat-form-field>
<input matInput required placeholder="Password" formControlName="password" [type]="!hide ? 'password' : 'text'"
[errorStateMatcher]="parentErrorStateMatcher">
<fa-icon matSuffix [icon]="!hide ? 'eye-slash' : 'eye'" (click)="hide = !hide"></fa-icon>
<mat-error>{{loginErrorMsg}}</mat-error>
</mat-form-field>
<!-- forgot password button -->
<br>
<button type="button" class='forgot-password-button' (click)="resetPassword()" mat-button>Forgot Password?</button>
<!-- login button -->
<br>
<!-- TODO: Next to each other...-->
<button mat-raised-button color="primary" class='login-button' type="submit" translate>Login</button>
<button mat-raised-button *ngIf="areGuestsEnabled()" color="primary" class='login-button' type="button"
(click)="guestLogin()" translate>Login as Guest</button>
</form>
</div>

View File

@ -2,23 +2,6 @@ mat-form-field {
width: 100%; width: 100%;
} }
header {
width: 100%;
flex: 1;
mat-toolbar {
min-height: 200px !important;
}
.login-logo-bar {
img {
margin-left: auto;
margin-right: auto;
width: 90%;
height: 90%;
max-width: 400px;
}
}
}
.forgot-password-button { .forgot-password-button {
float: right; float: right;
padding: 0; padding: 0;
@ -49,7 +32,3 @@ header {
margin: 0 auto; margin: 0 auto;
max-width: 400px; max-width: 400px;
} }
footer {
bottom: 0; //black magic to keep the toolbar active here
}

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginMaskComponent } from './login-mask.component';
describe('LoginMaskComponent', () => {
let component: LoginMaskComponent;
let fixture: ComponentFixture<LoginMaskComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LoginMaskComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginMaskComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,6 +1,5 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { BaseComponent } from 'app/base.component'; import { BaseComponent } from 'app/base.component';
import { AuthService } from 'app/core/services/auth.service'; import { AuthService } from 'app/core/services/auth.service';
@ -10,7 +9,8 @@ import { FormControl, FormGroupDirective, NgForm, FormGroup, Validators, FormBui
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { HttpErrorResponse, HttpClient } from '@angular/common/http'; import { HttpErrorResponse, HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment'; import { environment } from 'environments/environment';
import { OpenSlidesService } from '../../core/services/openslides.service'; import { OpenSlidesService } from '../../../../core/services/openslides.service';
import { LoginDataService } from '../../../../core/services/login-data.service';
/** /**
* Custom error states. Might become part of the shared module later. * Custom error states. Might become part of the shared module later.
@ -32,16 +32,16 @@ export class ParentErrorStateMatcher implements ErrorStateMatcher {
} }
/** /**
* Login component. * Login mask component.
* *
* Handles user (and potentially guest) login * Handles user and guest login
*/ */
@Component({ @Component({
selector: 'os-login', selector: 'os-login-mask',
templateUrl: './login.component.html', templateUrl: './login-mask.component.html',
styleUrls: ['./login.component.scss'] styleUrls: ['./login-mask.component.scss']
}) })
export class LoginComponent extends BaseComponent implements OnInit, OnDestroy { 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
*/ */
@ -72,24 +72,19 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy {
*/ */
public inProcess = false; public inProcess = false;
/**
* The provacy policy send by the server.
*
* TODO: give an option to show it during login.
*/
public privacyPolicy: string;
/** /**
* Constructor for the login component * Constructor for the login component
* *
* @param titleService Setting the title
* @param authService Authenticating the user * @param authService Authenticating the user
* @param operator The representation of the current user * @param operator The representation of the current user
* @param router forward to start page * @param router forward to start page
* @param formBuilder To build the form and validate * @param formBuilder To build the form and validate
* @param http used to get information before the login
* @param matSnackBar Display information
* @param OpenSlides The Service for OpenSlides
* @param loginDataService provide information about the legal notice and privacy policy
*/ */
public constructor( public constructor(
protected titleService: Title,
protected translate: TranslateService, protected translate: TranslateService,
private authService: AuthService, private authService: AuthService,
private operator: OperatorService, private operator: OperatorService,
@ -97,9 +92,10 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy {
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private http: HttpClient, private http: HttpClient,
private matSnackBar: MatSnackBar, private matSnackBar: MatSnackBar,
private OpenSlides: OpenSlidesService private OpenSlides: OpenSlidesService,
private loginDataService: LoginDataService
) { ) {
super(titleService, translate); super();
this.createForm(); this.createForm();
} }
@ -110,15 +106,15 @@ export class LoginComponent extends BaseComponent implements OnInit, OnDestroy {
* Observes the operator, if a user was already logged in, recreate to user and skip the login * Observes the operator, if a user was already logged in, recreate to user and skip the login
*/ */
public ngOnInit(): void { public ngOnInit(): void {
super.setTitle('Login'); // Get the login data. Save information to the login data service
this.http.get<any>(environment.urlPrefix + '/users/login/', {}).subscribe(response => { this.http.get<any>(environment.urlPrefix + '/users/login/', {}).subscribe(response => {
if (response.info_text) { if (response.info_text) {
this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), { this.installationNotice = this.matSnackBar.open(response.info_text, this.translate.instant('OK'), {
duration: 5000 duration: 5000
}); });
} }
this.privacyPolicy = response.privacy_policy; this.loginDataService.setPrivacyPolicy(response.privacy_policy);
this.loginDataService.setLegalNotice(response.legal_notice);
}); });
} }

View File

@ -0,0 +1,9 @@
<main>
<mat-toolbar color="primary" translate>
Privacy Policy
</mat-toolbar>
<os-privacy-policy-content></os-privacy-policy-content>
<button mat-raised-button color="accent" routerLink="/login" routerLinkActive="router-link-active" translate>Login</button>
</main>

View File

@ -0,0 +1,24 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginPrivacyPolicyComponent } from './login-privacy-policy.component';
describe('LoginPrivacyPolicyComponent', () => {
let component: LoginPrivacyPolicyComponent;
let fixture: ComponentFixture<LoginPrivacyPolicyComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LoginPrivacyPolicyComponent]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LoginPrivacyPolicyComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,22 @@
import { Component, OnInit } from '@angular/core';
/**
* Container to display the privacy policy on the login page.
* Uses the corresponding shared component
*/
@Component({
selector: 'os-login-privacy-policy',
templateUrl: './login-privacy-policy.component.html',
styleUrls: ['./login-privacy-policy.component.scss', '../../assets/login-info-pages.scss']
})
export class LoginPrivacyPolicyComponent implements OnInit {
/**
* Empty Constructor
*/
public constructor() {}
/**
* Empty onInit
*/
public ngOnInit(): void {}
}

View File

@ -0,0 +1,12 @@
<!-- The actual form -->
<header>
<mat-toolbar class="login-logo-bar" color="primary">
<img src='/assets/img/openslides-logo-h-dark-transparent.svg' alt='OpenSlides-logo'>
</mat-toolbar>
</header>
<main>
<router-outlet></router-outlet>
</main>
<footer class="page-footer">
<os-footer></os-footer>
</footer>

View File

@ -0,0 +1,16 @@
header {
width: 100%;
flex: 1;
mat-toolbar {
min-height: 200px !important;
}
.login-logo-bar {
img {
margin-left: auto;
margin-right: auto;
width: 90%;
height: 90%;
max-width: 400px;
}
}
}

View File

@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { BaseComponent } from '../../../../base.component';
/**
* Login component.
*
* Serves as container for the login mask, legal notice and privacy policy
*/
@Component({
selector: 'os-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class LoginComponent extends BaseComponent implements OnInit {
/**
* Imports the title service and the translate service
*
* @param titleService to set the title
* @param translate just needed because super.setTitle depends in the `translator.instant` function
*/
public constructor(protected titleService: Title, protected translate: TranslateService) {
super(titleService, translate);
}
/**
* sets the title of the page
*/
public ngOnInit(): void {
super.setTitle('Login');
}
}

View File

@ -1,38 +0,0 @@
<!-- The actual form -->
<header>
<mat-toolbar class="login-logo-bar" color="primary">
<img src='/assets/img/openslides-logo-h-dark-transparent.svg' alt='OpenSlides-logo'>
</mat-toolbar>
</header>
<main>
<div class="form-wrapper">
<mat-spinner *ngIf="inProcess"></mat-spinner>
<form [formGroup]="loginForm" class="login-form" (ngSubmit)="formLogin()">
<mat-form-field>
<input matInput required placeholder="User name" formControlName="username" [errorStateMatcher]="parentErrorStateMatcher">
</mat-form-field>
<br>
<mat-form-field>
<input matInput required placeholder="Password" formControlName="password" [type]="!hide ? 'password' : 'text'" [errorStateMatcher]="parentErrorStateMatcher">
<fa-icon matSuffix [icon]="!hide ? 'eye-slash' : 'eye'" (click)="hide = !hide"></fa-icon>
<mat-error>{{loginErrorMsg}}</mat-error>
</mat-form-field>
<!-- forgot password button -->
<br>
<button type="button" class='forgot-password-button' (click)="resetPassword()" mat-button>Forgot Password?</button>
<!-- login button -->
<br>
<!-- TODO: Next to each other...-->
<button mat-raised-button color="primary" class='login-button' type="submit" translate>Login</button>
<button mat-raised-button *ngIf="areGuestsEnabled()" color="primary" class='login-button' type="button" (click)="guestLogin()" translate>Login as Guest</button>
</form>
</div>
</main>
<footer class="page-footer">
<os-footer></os-footer>
</footer>

View File

@ -1,10 +1,15 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { LoginComponent } from './login.component'; import { RouterModule } from '@angular/router';
import { LoginComponent } from './components/login-wrapper/login.component';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { LoginMaskComponent } from './components/login-mask/login-mask.component';
import { LoginLegalNoticeComponent } from './components/login-legal-notice/login-legal-notice.component';
import { LoginPrivacyPolicyComponent } from './components/login-privacy-policy/login-privacy-policy.component';
@NgModule({ @NgModule({
imports: [CommonModule, SharedModule], imports: [CommonModule, RouterModule, SharedModule],
declarations: [LoginComponent] declarations: [LoginComponent, LoginMaskComponent, LoginLegalNoticeComponent, LoginPrivacyPolicyComponent]
}) })
export class LoginModule {} export class LoginModule {}

View File

@ -1,27 +1,3 @@
<mat-toolbar color='primary'> <os-head-bar appName="Privacy Policy"></os-head-bar>
<span class='app-name on-transition-fade' translate>Privacy Policy</span>
</mat-toolbar>
<mat-card class="os-card on-transition-fade"> <os-privacy-policy-content></os-privacy-policy-content>
<div>
OpenSlides speichert nur so viele personenbezogene Daten wie unbedingt nötig sind, um Besuchern Informationen und Dienste
zuverlässig und sicher anbieten zu können. Eine Auswertung der Dienste-Protokolle erfolgt nur von uns selbst und
auch nur, um mögliche Fehler, Einbruchsversuche oder technisches Verhalten der Server auf unseren Server zu analysieren.
Eine Weitergabe von Teilen der erhobenen Daten erfolgt ausschließlich gemäß gesetzlicher Verpflichtung z.B. an Strafverfolgungs-
oder Steuerbehörden. Nachfolgend wird genau aufgeschlüsselt, bei welcher Gelegenheit welche Daten wie lange gespeichert
sind. Zudem wird beschrieben, welche Schritte erforderlich sind um Daten zu löschen.
<h3 translate>Cookies</h3>
Beim Besuch der Website wird ein sogenanntes Cookie angelegt. Dieses Cookie wird ausschließlich dazu verwendet, um auf der
Website eingeloggt zu bleiben. Ein sogenanntes "Tracking Cookie" wird nicht verwendet.
<h3 translate>Logfiles</h3>
Zu OpenSlides gehören verschiedene Unter-Services. Diese loggen folgende Informationen: Quell-IP-Adresse, Zeitstempel, genutztes
Betriebssystem, verwendeter Web-Browser, Referer-URL, E-Mail-Adresse und besuchte Seite.
<h3 translate>Database</h3>
Als Mitglied werden folgende Daten von Ihnen gespeichert: Titel, Vorname, Nachname, EMail, Gliederungsebene, Teilnehmernummer,
Gruppenzugehörigkeit, Initiales Passwort im Klartext, Vergebenes Passwort als kryptografischer Hashwert und ein Kommentar
für interne Notizen. Diese Informationen werden mit den Aktionen innerhalb von OpenSlides in Verbindung gebracht.
Diese Informationen werden nicht an Dritte weiter gegeben und sind auch nicht für diese zugänglich.
<h3 translate>Deleting Files</h3>
Die Daten dieser OpenSlides-Demo-Instanz werden nächtlich automatisch gelöscht.
</div>
</mat-card>

View File

@ -1,3 +0,0 @@
mat-card {
height: 100%;
}

View File

@ -7,7 +7,6 @@ import { StartComponent } from './start/start.component';
import { PrivacyPolicyComponent } from './privacy-policy/privacy-policy.component'; import { PrivacyPolicyComponent } from './privacy-policy/privacy-policy.component';
import { LegalNoticeComponent } from './legal-notice/legal-notice.component'; import { LegalNoticeComponent } from './legal-notice/legal-notice.component';
import { AuthGuard } from '../core/services/auth-guard.service'; import { AuthGuard } from '../core/services/auth-guard.service';
// import { LoginComponent } from './login/login.component';
/** /**
* Routung to all OpenSlides apps * Routung to all OpenSlides apps
@ -15,15 +14,26 @@ import { AuthGuard } from '../core/services/auth-guard.service';
* TODO: Plugins will have to append to the Routes-Array * TODO: Plugins will have to append to the Routes-Array
*/ */
const routes: Routes = [ const routes: Routes = [
// { path: 'login', component: LoginComponent },
{ {
path: '', path: '',
component: SiteComponent, component: SiteComponent,
children: [ children: [
{ path: '', component: StartComponent }, {
{ path: 'legalnotice', component: LegalNoticeComponent }, path: '',
{ path: 'privacypolicy', component: PrivacyPolicyComponent }, component: StartComponent
{ path: 'agenda', loadChildren: './agenda/agenda.module#AgendaModule' }, },
{
path: 'legalnotice',
component: LegalNoticeComponent
},
{
path: 'privacypolicy',
component: PrivacyPolicyComponent
},
{
path: 'agenda',
loadChildren: './agenda/agenda.module#AgendaModule'
},
{ {
path: 'assignments', path: 'assignments',
loadChildren: './assignments/assignments.module#AssignmentsModule' loadChildren: './assignments/assignments.module#AssignmentsModule'
@ -32,12 +42,18 @@ const routes: Routes = [
path: 'mediafiles', path: 'mediafiles',
loadChildren: './mediafiles/mediafiles.module#MediafilesModule' loadChildren: './mediafiles/mediafiles.module#MediafilesModule'
}, },
{ path: 'motions', loadChildren: './motions/motions.module#MotionsModule' }, {
path: 'motions',
loadChildren: './motions/motions.module#MotionsModule'
},
{ {
path: 'settings', path: 'settings',
loadChildren: './settings/settings.module#SettingsModule' loadChildren: './settings/settings.module#SettingsModule'
}, },
{ path: 'users', loadChildren: './users/users.module#UsersModule' } {
path: 'users',
loadChildren: './users/users.module#UsersModule'
}
], ],
canActivateChild: [AuthGuard] canActivateChild: [AuthGuard]
} }

View File

@ -44,14 +44,14 @@ export class SiteComponent extends BaseComponent implements OnInit {
*/ */
public constructor( public constructor(
private authService: AuthService, private authService: AuthService,
operator: OperatorService, public operator: OperatorService,
public vp: ViewportService, public vp: ViewportService,
public translate: TranslateService, public translate: TranslateService,
public dialog: MatDialog public dialog: MatDialog
) { ) {
super(); super();
operator.getObservable().subscribe(user => { this.operator.getObservable().subscribe(user => {
if (user) { if (user) {
this.username = user.full_name; this.username = user.full_name;
} else { } else {

View File

@ -94,11 +94,11 @@ export class StartComponent extends BaseComponent implements OnInit {
console.log('the user: ', user1fromStore); console.log('the user: ', user1fromStore);
console.log('remove a single user:'); console.log('remove a single user:');
this.DS.remove(User, 100); // this.DS.remove(User, 100);
console.log('remove more users'); console.log('remove more users');
this.DS.remove(User, 200, 201, 202); // this.DS.remove(User, 200, 201, 202);
console.log('remove an array of users'); console.log('remove an array of users');
this.DS.remove(User, ...[321, 363, 399]); // this.DS.remove(User, ...[321, 363, 399]);
console.log('test filter: '); console.log('test filter: ');
console.log(this.DS.filter<User>(User, user => user.id === 1)); console.log(this.DS.filter<User>(User, user => user.id === 1));

View File

@ -453,8 +453,10 @@ class UserLoginView(APIView):
password='<strong>admin</strong>') password='<strong>admin</strong>')
else: else:
context['info_text'] = '' context['info_text'] = ''
# Add the privacy policy, so the client can display it even, it is not logged in. # Add the privacy policy and legal notice, so the client can display it
# even, it is not logged in.
context['privacy_policy'] = config['general_event_privacy_policy'] context['privacy_policy'] = config['general_event_privacy_policy']
context['legal_notice'] = config['general_event_legal_notice']
else: else:
# self.request.method == 'POST' # self.request.method == 'POST'
context['user_id'] = self.user.pk context['user_id'] = self.user.pk