diff --git a/package-lock.json b/package-lock.json index 9f668abd2ce1916a35a7a42f30b4f8e52d09ec9e..3d5abe1683a7c01b3fc6824fd7a7a5f7d45bbc90 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5813,9 +5813,9 @@ "dev": true }, "iqb-components": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/iqb-components/-/iqb-components-1.4.7.tgz", - "integrity": "sha512-mOPwBhzC1Pd5TXdrMgpFnoepFiAmN807TEoU3tQZQpPT4TVR03K33noc05gh4ljR7FPvGjVtaUvFxpRiIibXyA==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/iqb-components/-/iqb-components-1.6.1.tgz", + "integrity": "sha512-plAWvMTwCaJPVNgq1dlb76lNQKqniGS9Yiz7VGJCd1Z/8DgFDiemCtLd2UVqNJ7sQDdpx2k3JEztuTk1S2fA9w==", "requires": { "tslib": "^1.9.0" } diff --git a/package.json b/package.json index 4f58dd1fda1cf8b192114c0ef3051607911ac41d..91dcff65453af7e54b38e5d30a443f620dc7c2b9 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "chromedriver": "^79.0.0", "classlist.js": "^1.1.20150312", "core-js": "~2.6.11", - "iqb-components": "1.4.7", + "iqb-components": "1.6.x", "material-design-icons": "~3.0.1", "rxjs": "~6.4.0", "tslib": "~1.9.0", diff --git a/src/app/about/about.component.html b/src/app/about/about.component.html index edd56307dab23d2d441942162cb1fd2a5ccdf33a..51ae13ab7b182bfeef4751f4e2fa8f56822a683f 100644 --- a/src/app/about/about.component.html +++ b/src/app/about/about.component.html @@ -1,6 +1,6 @@ <div class="logo"> <a [routerLink]="['/']"> - <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite" alt="IQB-logo"/> </a> </div> <div class="page-body"> @@ -46,8 +46,10 @@ E-Mail: datenschutz@uv.hu-berlin.de<br/> <a href="http://www.hu-berlin.de/de/datenschutz" target="_blank">www.hu-berlin.de/de/datenschutz</a> </p> - <p><a [routerLink]="['/']"><i class="material-icons">arrow_back</i> zurück zur Startseite</a></p> </mat-card-content> + <mat-card-actions> + <button (click)="goBack()" mat-raised-button color="primary"><i class="material-icons">arrow_back</i> zurück zur Startseite</button> + </mat-card-actions> </mat-card> </div> </div> diff --git a/src/app/about/about.component.ts b/src/app/about/about.component.ts index db504334651dda7d5a378d1b04ae14bda1876221..40de7b49743c6e66ae6e7f5616c14f7728d959d1 100644 --- a/src/app/about/about.component.ts +++ b/src/app/about/about.component.ts @@ -1,5 +1,6 @@ import { MainDataService } from 'src/app/maindata.service'; import { Component, Inject } from '@angular/core'; +import { Router } from '@angular/router'; @Component({ templateUrl: './about.component.html' @@ -10,6 +11,11 @@ export class AboutComponent { @Inject('APP_NAME') public appName: string, @Inject('APP_PUBLISHER') public appPublisher: string, @Inject('APP_VERSION') public appVersion: string, + private router: Router, public mds: MainDataService ) { } + + goBack() { + this.router.navigate(['/']); + } } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3ac1b21b34d3112dff58b62b5ec35e9643a4c8f8..1afb1784d652c4d1b990eb5a64667d424045b0db 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,7 +1,8 @@ import { MainDataService } from './maindata.service'; import { Component, OnInit } from '@angular/core'; -import { BackendService, ServerError } from './backend.service'; +import { BackendService } from './backend.service'; import { LoginData } from './app.interfaces'; +import { ServerError } from 'iqb-components'; @Component({ selector: 'tc-root', diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 18cae91f3d7d859e9975ebfee44fc8b4a32c12ba..23fe27f1a38420d0e989f344336d8184063744dd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -2,7 +2,7 @@ import { AboutComponent } from './about/about.component'; import { BrowserModule } from '@angular/platform-browser'; import { HttpClientModule } from '@angular/common/http'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { NgModule, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { MatButtonModule, MatCheckboxModule, MatMenuModule, MatTooltipModule, MatCardModule, @@ -11,7 +11,6 @@ import { MatButtonModule, MatCheckboxModule, MatMenuModule, MatTooltipModule, Ma import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; -import { IqbComponents } from 'iqb-components'; import { BackendService } from './backend.service'; import { StartComponent } from './start/start.component'; import { LocationStrategy, HashLocationStrategy } from '@angular/common'; @@ -50,8 +49,7 @@ import { CustomTextPipe } from './custom-text.pipe'; ReactiveFormsModule, HttpClientModule, MatToolbarModule, - AppRoutingModule, - IqbComponents + AppRoutingModule ], providers: [ BackendService, diff --git a/src/app/backend.service.ts b/src/app/backend.service.ts index b24154e100a11de855658e61028d96f847c1be64..e232513003be230468f01728ec9d1c28eb87ab9f 100644 --- a/src/app/backend.service.ts +++ b/src/app/backend.service.ts @@ -1,42 +1,10 @@ import { Injectable, Inject } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpEvent, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { Observable, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; -import { LoginData, BookletStatus, PersonTokenAndBookletDbId, BookletData, BookletDataListByCode, - KeyValuePair } from './app.interfaces'; - -// ============================================================================ -// class instead of interface to be able to use instanceof to check type -export class ServerError { - public code: number; - public labelNice: string; - public labelSystem: string; - constructor(code: number, labelNice: string, labelSystem: string) { - this.code = code; - this.labelNice = labelNice; - this.labelSystem = labelSystem; - } -} - -// ============================================================================ -export class ErrorHandler { - public static handle(errorObj: HttpErrorResponse): Observable<ServerError> { - let myreturn: ServerError = null; - if (errorObj.error instanceof ErrorEvent) { - myreturn = new ServerError(500, 'Verbindungsproblem', (<ErrorEvent>errorObj.error).message); - } else { - myreturn = new ServerError(errorObj.status, 'Verbindungsproblem', errorObj.message); - if (errorObj.status === 401) { - myreturn.labelNice = 'Zugriff verweigert - bitte (neu) anmelden!'; - } else if (errorObj.status === 503) { - myreturn.labelNice = 'Achtung: Server meldet Datenbankproblem.'; - } - } - - return of(myreturn); - } -} +import { LoginData, BookletStatus, PersonTokenAndBookletDbId, KeyValuePair } from './app.interfaces'; +import {ErrorHandler, ServerError} from "iqb-components"; // ============================================================================ @Injectable() @@ -45,7 +13,7 @@ export class BackendService { private serverSlimUrl_Close = ''; constructor( - @Inject('SERVER_URL') private serverUrl: string, + @Inject('SERVER_URL') private readonly serverUrl: string, private http: HttpClient) { this.serverSlimUrl = this.serverUrl + 'php_tc/login.php/'; this.serverSlimUrl_Close = this.serverUrl + 'php_tc/tc_post.php/'; @@ -75,7 +43,7 @@ export class BackendService { getSysConfig(): Observable<KeyValuePair> { return this.http.get<KeyValuePair>(this.serverSlimUrl + 'sysconfig') .pipe( - catchError(e => of(null)) + catchError(() => of(null)) ); } diff --git a/src/app/errormsg/errormsg.component.ts b/src/app/errormsg/errormsg.component.ts index 94527f5d8c5f299686f2d378a26e3f0b898c5f43..df70027d3ae4caf7259f4f934bdd05a903db5022 100644 --- a/src/app/errormsg/errormsg.component.ts +++ b/src/app/errormsg/errormsg.component.ts @@ -1,4 +1,4 @@ -import { ServerError } from '../backend.service'; +import { ServerError } from 'iqb-components'; import { MainDataService } from '../maindata.service'; import { Component, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; diff --git a/src/app/maindata.service.ts b/src/app/maindata.service.ts index c6eec865fd0d19bb890c1eca80c21734c3f5c74e..02cf855b4e79b0434c3a0e22f737990ac1f17a01 100644 --- a/src/app/maindata.service.ts +++ b/src/app/maindata.service.ts @@ -1,8 +1,9 @@ import { KeyValuePair } from './test-controller/test-controller.interfaces'; -import { ServerError, BackendService } from './backend.service'; +import { BackendService } from './backend.service'; import { BehaviorSubject, Subject, forkJoin } from 'rxjs'; import { Injectable } from '@angular/core'; import { LoginData } from './app.interfaces'; +import {ServerError} from "iqb-components"; @Injectable({ providedIn: 'root' @@ -152,7 +153,7 @@ export class MainDataService { forkJoin( this.bs.addBookletLogClose(myLoginData.booklet), this.bs.lockBooklet(myLoginData.booklet) - ).subscribe(ok => { + ).subscribe(() => { myLoginData.booklet = 0; myLoginData.bookletlabel = ''; this.setNewLoginData(myLoginData); diff --git a/src/app/start/start-button-data.class.ts b/src/app/start/start-button-data.class.ts index 4f4bb52e2c6d5f4fbb3b8b9082a6033fd7ad5bed..802a0c809275263fc4e48ad484266310ac72ccf8 100644 --- a/src/app/start/start-button-data.class.ts +++ b/src/app/start/start-button-data.class.ts @@ -1,6 +1,7 @@ import { map } from 'rxjs/operators'; -import { BackendService, ServerError } from '../backend.service'; +import { BackendService } from '../backend.service'; import { BookletStatus } from '../app.interfaces'; +import { ServerError } from 'iqb-components'; // import { of } from 'rxjs'; // import { pipe } from '@angular/core/src/render3'; diff --git a/src/app/start/start.component.ts b/src/app/start/start.component.ts index 1c21d8143e4fa2f4a3b2f347f795490c413155b6..9d044b67e0d1c96393d928edb3b6f2ab9a1469c7 100644 --- a/src/app/start/start.component.ts +++ b/src/app/start/start.component.ts @@ -1,12 +1,12 @@ -import { MainDataService } from './../maindata.service'; -import { Subscription, BehaviorSubject, forkJoin } from 'rxjs'; -import { MessageDialogComponent, MessageDialogData, MessageType } from 'iqb-components'; +import { MainDataService } from '../maindata.service'; +import { Subscription, forkJoin } from 'rxjs'; +import { MessageDialogComponent, MessageDialogData, MessageType, ServerError } from 'iqb-components'; import { MatDialog } from '@angular/material'; -import { BackendService, ServerError } from '../backend.service'; -import { PersonTokenAndBookletDbId, BookletDataListByCode, LoginData, BookletStatus } from '../app.interfaces'; +import { BackendService } from '../backend.service'; +import { PersonTokenAndBookletDbId, LoginData } from '../app.interfaces'; import { Router } from '@angular/router'; -import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; -import { FormGroup, FormBuilder, FormArray, FormControl, Validators, ReactiveFormsModule } from '@angular/forms'; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { StartButtonData } from './start-button-data.class'; @Component({ @@ -193,9 +193,9 @@ export class StartComponent implements OnInit, OnDestroy { } else { this.mds.globalErrorMsg$.next(null); this.mds.refreshCostumTexts = false; - this.mds.setCostumTextsLogin(loginData.costumTexts); + this.mds.setCostumTextsLogin((loginData as LoginData).costumTexts); this.mds.refreshCostumTexts = true; - this.mds.setNewLoginData(loginData); + this.mds.setNewLoginData(loginData as LoginData); } this.dataLoading = false; } diff --git a/src/app/sys-check/backend.service.ts b/src/app/sys-check/backend.service.ts index e919ce2ce444213421bf51a23811d688a880faac..5b8ffc5d2a474c80ae15942d5b15fb7b44bf37db 100644 --- a/src/app/sys-check/backend.service.ts +++ b/src/app/sys-check/backend.service.ts @@ -1,63 +1,25 @@ -import { CheckConfig } from './backend.service'; +import {CheckConfig, CheckConfigData, NetworkRequestTestResult, ReportEntry, UnitData} from './sys-check.interfaces'; import { Injectable, Inject } from '@angular/core'; -import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; import { Observable, of } from 'rxjs'; -import {catchError, map, tap} from 'rxjs/operators'; -import { ServerError } from '../backend.service'; +import { catchError, map, tap } from 'rxjs/operators'; +import { ErrorHandler, ServerError } from 'iqb-components'; @Injectable({ providedIn: 'root' }) export class BackendService { - public basicTestConfig: CheckConfig = { - id: 'Basistest', - label: 'Basistest', - description: 'Es wird nur ein Bericht zu grundlegenden Systemeigenschaften und zur Netzverbindung gegeben.' - }; - public basicTestConfigData: CheckConfigData = { - id: 'Basistest', - label: 'Basistest', - questions: [], - hasunit: false, - cansave: false, - questionsonlymode: false, - ratings: [], - skipnetwork: false, - downloadMinimum: 1.875e+6, // 15Mbit/s ~> typical dl speed 4G CAT4 - downloadGood: 3.75e+6, // 30Mbit/s ~> typical dl speed 4G+ CAT6 - uploadMinimum: 250000, // 2Mbit/s - uploadGood: 1.25e+6, // 10Mbit/s - }; - private serverSlimUrl_GET: string; constructor( - @Inject('SERVER_URL') private serverUrl: string, + @Inject('SERVER_URL') private readonly serverUrl: string, private http: HttpClient) { this.serverUrl = this.serverUrl + 'php_tc/'; this.serverSlimUrl_GET = this.serverUrl + 'tc_get.php/'; } - - // TODO there is a copy of this in testController/backendService -> move to common ancestor - static errorHandle(errorObj: HttpErrorResponse): Observable<ServerError> { - let myreturn: ServerError = null; - if (errorObj.error instanceof ErrorEvent) { - myreturn = new ServerError(500, 'Verbindungsproblem', (<ErrorEvent>errorObj.error).message); - } else { - myreturn = new ServerError(errorObj.status, 'Verbindungsproblem', errorObj.message); - if (errorObj.status === 401) { - myreturn.labelNice = 'Zugriff verweigert - bitte (neu) anmelden!'; - } else if (errorObj.status === 504) { - myreturn.labelNice = 'Achtung: Server meldet Datenbankproblem.'; - } - } - return of(myreturn); - } - - getCheckConfigs(): Observable<CheckConfig[]> { const httpOptions = { headers: new HttpHeaders({ @@ -67,14 +29,13 @@ export class BackendService { return this.http .post<CheckConfig[]>(this.serverUrl + 'getSysCheckConfigs.php', {}, httpOptions) .pipe( - catchError(problem_data => { + catchError(() => { const myreturn: CheckConfig[] = []; return of(myreturn); }) ); } - getCheckConfigData(cid: string): Observable<CheckConfigData> { const httpOptions = { headers: new HttpHeaders({ @@ -84,7 +45,7 @@ export class BackendService { return this.http .post<CheckConfigData>(this.serverUrl + 'getSysCheckConfigData.php', {c: cid}, httpOptions) .pipe( - catchError(problem_data => { + catchError(() => { const myreturn: CheckConfigData = null; return of(myreturn); }) @@ -103,7 +64,7 @@ export class BackendService { .post<boolean>(this.serverUrl + 'saveSysCheckReport.php', {c: cid, k: keyphrase, t: title, e: envResults, n: networkResults, q: questionnaireResults, u: unitResults}, httpOptions) .pipe( - catchError(problem_data => { + catchError(() => { return of(false); }) ); @@ -125,7 +86,7 @@ export class BackendService { data.duration = BackendService.getMostPreciseTimestampBrowserCanProvide() - startingTime; return data; }), - catchError(BackendService.errorHandle) + catchError(ErrorHandler.handle) ); } @@ -142,6 +103,9 @@ export class BackendService { }; return new Promise(function(resolve, reject) { + if (reject) { + console.log('reject is set'); + } const xhr = new XMLHttpRequest(); xhr.open('POST', serverUrl + 'doSysCheckDownloadTest.php?size=' + @@ -153,7 +117,8 @@ export class BackendService { if (xhr.status !== 200) { testResult.error = `Error ${xhr.statusText} (${xhr.status}) `; } - if (xhr.response.toString().length !== requestedDownloadSize) { + // tslint:disable-next-line:triple-equals + if (xhr.response.toString().length != requestedDownloadSize) { testResult.error = `Error: Data package has wrong size! ${requestedDownloadSize} ` + xhr.response.toString().length; } const currentTime = testResult.duration = BackendService.getMostPreciseTimestampBrowserCanProvide(); @@ -193,6 +158,9 @@ export class BackendService { }; return new Promise(function (resolve, reject) { + if (reject) { + console.log('reject is set'); + } const xhr = new XMLHttpRequest(); xhr.open('POST', serverUrl + 'doSysCheckUploadTest.php', true); @@ -215,7 +183,8 @@ export class BackendService { const response = JSON.parse(xhr.response); const arrivingSize = parseFloat(response['packageReceivedSize']); - if (arrivingSize !== requestedUploadSize) { + // tslint:disable-next-line:triple-equals + if (arrivingSize != requestedUploadSize) { testResult.error = `Error: Data package has wrong size! ${requestedUploadSize} != ${arrivingSize}`; } } catch (e) { @@ -267,69 +236,5 @@ export class BackendService { } return randomString; } - -// end of network check functions -// 7777777777777777777777777777777777777777777777777777777777777777777777 - -} // end of backend service - -export interface CheckConfig { - id: string; - label: string; - description: string; -} - -export interface Rating { - type: string; - min: number; - good: number; - value: string; -} - -export interface CheckConfigData { - id: string; - label: string; - questions: FormDefEntry[]; - hasunit: boolean; - cansave: boolean; - questionsonlymode: boolean; - ratings: Rating[]; - skipnetwork: boolean; - uploadMinimum: number; - uploadGood: number; - downloadMinimum: number; - downloadGood: number; -} - -export interface FormDefEntry { - id: string; - type: string; - prompt: string; - value: string; - options: string[]; -} - -export interface UnitData { - key: string; - label: string; - def: string; - player: string; - player_id: string; - duration: number; } -export interface NetworkRequestTestResult { - 'type': 'downloadTest' | 'uploadTest'; - 'size': number; - 'duration': number; - 'error': string | null; - 'speedInBPS': number; -} - -export interface ReportEntry { - id: string; - type: string; - label: string; - value: string; - warning: boolean; -} diff --git a/src/app/sys-check/environment-check/environment-check.component.html b/src/app/sys-check/environment-check/environment-check.component.html index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d69108220e34821e8583028904aed1d83a991ced 100644 --- a/src/app/sys-check/environment-check/environment-check.component.html +++ b/src/app/sys-check/environment-check/environment-check.component.html @@ -0,0 +1,16 @@ +<mat-card-header> + <mat-card-title> + Computer/Browser + </mat-card-title> +</mat-card-header> + +<mat-card-content> + <table fxFlex="100%"> + <tbody> + <tr *ngFor="let ed of data"> + <td>{{ed.label}}</td> + <td> {{ed.value}}</td> + </tr> + </tbody> + </table> +</mat-card-content> diff --git a/src/app/sys-check/environment-check/environment-check.component.ts b/src/app/sys-check/environment-check/environment-check.component.ts index a14bb61897ce9ea259e1acd2e8f906ce4aa3077f..9531d6407cd95c7b0079441d54689aef8da6d608 100644 --- a/src/app/sys-check/environment-check/environment-check.component.ts +++ b/src/app/sys-check/environment-check/environment-check.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { SyscheckDataService } from '../syscheck-data.service'; -import {ReportEntry} from '../backend.service'; +import { ReportEntry } from '../sys-check.interfaces'; @Component({ selector: 'iqb-environment-check', @@ -9,6 +9,7 @@ import {ReportEntry} from '../backend.service'; export class EnvironmentCheckComponent implements OnInit { private report: Map<string, ReportEntry> = new Map<string, ReportEntry>(); + data: ReportEntry[] = []; private rating = { browser: { @@ -44,7 +45,8 @@ export class EnvironmentCheckComponent implements OnInit { const report = Array.from(this.report.values()) .sort((item1: ReportEntry, item2: ReportEntry) => (item1.label > item2.label) ? 1 : -1); - this.ds.environmentData$.next(Object.values(report)); + this.data = Object.values(report); + this.ds.environmentData$.next(this.data); } private reportPush(key: string, value: string, warning: boolean = false) { @@ -131,7 +133,7 @@ export class EnvironmentCheckComponent implements OnInit { getOS() { const userAgent = window.navigator.userAgent; - let osName = ''; + let osName; if (userAgent.indexOf('Windows') !== -1) { if (userAgent.indexOf('Windows NT 10.0') !== -1) { osName = 'Windows 10'; diff --git a/src/app/sys-check/network-check/network-check.component.html b/src/app/sys-check/network-check/network-check.component.html index e21512aa1f7df05959d4e621e6b1c98143f274a2..01ae4476320f8acf42a16ca285a58ecfc9a2d667 100644 --- a/src/app/sys-check/network-check/network-check.component.html +++ b/src/app/sys-check/network-check/network-check.component.html @@ -1,54 +1,42 @@ -<mat-card [ngStyle]="{display: measureNetwork ? 'block' : 'none'}"> - - <mat-card-header> - <mat-card-title> - Netzwerk - <span *ngIf="!status.done" style="color:red"> - Test läuft, bitte warten.</span> - </mat-card-title> - <mat-card-subtitle> - {{status.message}} - - <span *ngIf="status.done && (networkRating.overallRating !== 'N/A')"> - <span [ngSwitch]="networkRating.overallRating">Ihre Verbindung zum Testserver ist - <span *ngSwitchCase="'insufficient'" style="color: red; font-weight: bold;">unzureichend</span> - <span *ngSwitchCase="'ok'" style="color: orange; font-weight: bold;">vorauss. ausreichend</span> - <span *ngSwitchCase="'good'" style="color: green; font-weight: bold;">gut</span> - <span *ngSwitchCase="'unstable'" style="color: orangered; font-weight: bold;">sehr instabil</span> - </span>. - </span> - </mat-card-subtitle> - </mat-card-header> - - <mat-card-content> - - <div class="spinner-container-local" *ngIf="!status.done"> - <mat-spinner></mat-spinner> +<mat-card-header> + <mat-card-title> + Netzwerk + <span *ngIf="!status.done" style="color:red"> - Test läuft, bitte warten.</span> + </mat-card-title> + <mat-card-subtitle> + {{status.message}} + + <span *ngIf="status.done && (networkRating.overallRating !== 'N/A')"> + <span [ngSwitch]="networkRating.overallRating">Ihre Verbindung zum Testserver ist + <span *ngSwitchCase="'insufficient'" style="color: red; font-weight: bold;">unzureichend</span> + <span *ngSwitchCase="'ok'" style="color: orange; font-weight: bold;">vorauss. ausreichend</span> + <span *ngSwitchCase="'good'" style="color: green; font-weight: bold;">gut</span> + <span *ngSwitchCase="'unstable'" style="color: orangered; font-weight: bold;">sehr instabil</span> + </span>. + </span> + </mat-card-subtitle> +</mat-card-header> + +<mat-card-content> + <div fxLayout="row"> + + <div fxFlex="50%"> + <h4> + <span style="font-weight: normal">Geschwindigkeit Download: </span> + <span *ngIf="status.avgDownloadSpeedBytesPerSecond >= 0">⌀ {{humanReadableBytes(status.avgDownloadSpeedBytesPerSecond, true, false)}}/s</span> + <span *ngIf="status.avgDownloadSpeedBytesPerSecond < 0">Test noch nicht gestartet</span> + </h4> + <tc-speed-chart #downloadChart></tc-speed-chart> </div> - <div fxLayout="row"> - - <div fxFlex="50%"> - <h4> - <span style="font-weight: normal">Geschwindigkeit Download: </span> - <span *ngIf="status.avgDownloadSpeedBytesPerSecond >= 0">⌀ {{humanReadableBytes(status.avgDownloadSpeedBytesPerSecond, true, false)}}/s</span> - <span *ngIf="status.avgDownloadSpeedBytesPerSecond < 0">Test noch nicht gestartet</span> - </h4> - <tc-speed-chart #downloadChart></tc-speed-chart> - </div> - - <div fxFlex="50%"> - <h4> - <span style="font-weight: normal">Geschwindigkeit Upload: </span> - <span *ngIf="status.avgUploadSpeedBytesPerSecond >= 0">⌀ {{humanReadableBytes(status.avgUploadSpeedBytesPerSecond, true)}}/s</span> - <span *ngIf="status.avgUploadSpeedBytesPerSecond < 0">Test noch nicht gestartet</span> - </h4> - <tc-speed-chart #uploadChart></tc-speed-chart> - </div> - + <div fxFlex="50%"> + <h4> + <span style="font-weight: normal">Geschwindigkeit Upload: </span> + <span *ngIf="status.avgUploadSpeedBytesPerSecond >= 0">⌀ {{humanReadableBytes(status.avgUploadSpeedBytesPerSecond, true)}}/s</span> + <span *ngIf="status.avgUploadSpeedBytesPerSecond < 0">Test noch nicht gestartet</span> + </h4> + <tc-speed-chart #uploadChart></tc-speed-chart> </div> - <div *ngIf="!detectedNetworkInformations.available" style="color:red">Ihr Browser ist veraltet und verfügt über kein erweitertes Netzwerk-Profil.</div> - - </mat-card-content> - -</mat-card> + </div> +</mat-card-content> diff --git a/src/app/sys-check/network-check/network-check.component.ts b/src/app/sys-check/network-check/network-check.component.ts index 2ca8018c751bf380effa3e3fdcb5372ce84f448a..fa88720196b11366647076a3ff8963c575d5e493 100644 --- a/src/app/sys-check/network-check/network-check.component.ts +++ b/src/app/sys-check/network-check/network-check.component.ts @@ -1,52 +1,18 @@ import { SyscheckDataService } from '../syscheck-data.service'; import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { - BackendService, - NetworkRequestTestResult, - ReportEntry -} from '../backend.service'; +import {BackendService} from '../backend.service'; import { combineLatest } from 'rxjs'; - -enum BenchmarkType { - up, - down -} - -interface NetworkCheckStatus { - message: string; - avgUploadSpeedBytesPerSecond: number; - avgDownloadSpeedBytesPerSecond: number; - done: boolean; -} - -interface BenchmarkDefinition { - testSizes: number[]; - allowedDevianceBytesPerSecond: number; - allowedErrorsPerSequence: number; - allowedSequenceRepetitions: number; -} - -type TechCheckRating = 'N/A' | 'insufficient' | 'ok' | 'good' | 'unstable'; - -interface NetworkRating { - uploadRating: TechCheckRating; - downloadRating: TechCheckRating; - overallRating: TechCheckRating; -} - -interface DetectedNetworkInformations { - available: boolean; - downlinkMegabitPerSecond: number; - effectiveNetworkType: string; - roundTripTimeMs: number; - networkType: string; -} - +import { + DetectedNetworkInformation, + NetworkCheckStatus, + NetworkRating, NetworkRequestTestResult, ReportEntry +} from "../sys-check.interfaces"; @Component({ selector: 'iqb-network-check', templateUrl: './network-check.component.html' }) + export class NetworkCheckComponent implements OnInit { constructor( @@ -59,21 +25,6 @@ export class NetworkCheckComponent implements OnInit { @Input() measureNetwork: boolean; - readonly benchmarkDefinitions = new Map<BenchmarkType, BenchmarkDefinition>([ - [BenchmarkType.down, { - testSizes: [400000, 800000, 1600000, 3200000], - allowedDevianceBytesPerSecond: 100000, - allowedErrorsPerSequence: 0, - allowedSequenceRepetitions: 15 - }], - [BenchmarkType.up, { - testSizes: [100000, 200000, 400000, 800000], - allowedDevianceBytesPerSecond: 5000, - allowedErrorsPerSequence: 0, - allowedSequenceRepetitions: 15 - }] - ]); - public status: NetworkCheckStatus = { done: true, message: 'Messung noch nicht gestartet', @@ -81,10 +32,8 @@ export class NetworkCheckComponent implements OnInit { avgDownloadSpeedBytesPerSecond: -1 }; - private networkStats = new Map<BenchmarkType, number[]>([ - [BenchmarkType.down, []], - [BenchmarkType.up, []], - ]); + private networkStatsDownload: number[] = []; + private networkStatsUpload: number[] = []; public networkRating: NetworkRating = { downloadRating: 'N/A', @@ -92,7 +41,7 @@ export class NetworkCheckComponent implements OnInit { overallRating: 'N/A' }; - public detectedNetworkInformations: DetectedNetworkInformations = { + public detectedNetworkInformations: DetectedNetworkInformation = { downlinkMegabitPerSecond: null, effectiveNetworkType: null, roundTripTimeMs: null, @@ -113,7 +62,7 @@ export class NetworkCheckComponent implements OnInit { this.addBrowsersNativeNetworkInformationToReport(report); this.ds.networkData$.next(report); - combineLatest(this.ds.task$, this.ds.checkConfig$).subscribe(([task, checkConfig]) => { + combineLatest(this.ds.task$, this.ds.checkConfig$).subscribe(([task]) => { if (task === 'speedtest') { this.startCheck(); } @@ -129,34 +78,32 @@ export class NetworkCheckComponent implements OnInit { avgDownloadSpeedBytesPerSecond: -1 }; - this.networkStats = new Map<BenchmarkType, number[]>([ - [BenchmarkType.down, []], - [BenchmarkType.up, []], - ]); - console.log('start the loop'); - this.plotPrepare(BenchmarkType.down); - this.plotPrepare(BenchmarkType.up); + console.log('start the loop: '); + const myConfig = this.ds.checkConfig$.getValue(); + console.log(myConfig); + this.plotPrepare(true); + this.plotPrepare(false); - this.loopBenchmarkSequence(BenchmarkType.down) - .then(() => this.loopBenchmarkSequence(BenchmarkType.up)) + this.loopBenchmarkSequence(true) + .then(() => this.loopBenchmarkSequence(false)) .then(() => this.reportResults()) .catch(() => this.reportResults(true)); } - private plotPrepare(benchmarkType: BenchmarkType) { - - const testSizes = this.benchmarkDefinitions.get(benchmarkType).testSizes; + private plotPrepare(isDownloadPart: boolean) { + const myConfig = this.ds.checkConfig$.getValue(); + const testSizes = (isDownloadPart) ? myConfig.downloadspeed.sequenceSizes : myConfig.uploadspeed.sequenceSizes; const plotterSettings = { css: 'border: 1px solid silver; margin: 2px; width: 100%;', width: 800, - height: 140, + height: 240, labelPadding: 4, xAxisMaxValue: 16 + Math.max(...testSizes), xAxisMinValue: Math.min(...testSizes), - yAxisMaxValue: (BenchmarkType.down === benchmarkType) ? 1200 : 2500, - yAxisMinValue: (BenchmarkType.down === benchmarkType) ? 20 : 100, + yAxisMaxValue: (isDownloadPart) ? 1200 : 2500, + yAxisMinValue: (isDownloadPart) ? 20 : 100, xAxisStepSize: 4, - yAxisStepSize: (BenchmarkType.down === benchmarkType) ? 50 : 100, + yAxisStepSize: (isDownloadPart) ? 50 : 100, lineWidth: 5, xProject: x => (x === 0 ) ? 0 : Math.sign(x) * Math.log2(Math.abs(x)), yProject: y => (y === 0 ) ? 0 : Math.sign(y) * Math.log(Math.abs(y)), @@ -164,43 +111,55 @@ export class NetworkCheckComponent implements OnInit { yAxisLabels: (y, i) => (i < 10) ? this.humanReadableMilliseconds(y) : ' ', }; - if (benchmarkType === BenchmarkType.down) { + if (isDownloadPart) { this.downloadPlotter.reset(plotterSettings); - } - if (benchmarkType === BenchmarkType.up) { + } else { this.uploadPlotter.reset(plotterSettings); } } - private loopBenchmarkSequence(type: BenchmarkType): Promise<void> { - - this.updateStatus(`Benchmark Loop ${type} nr.:` + this.networkStats.get(type).length); - const benchmarkDefinition = this.benchmarkDefinitions.get(type); + private loopBenchmarkSequence(isDownloadPart: boolean): Promise<void> { + if (isDownloadPart) { + this.updateStatus(`Benchmark Loop Download nr.:` + this.networkStatsDownload.length); + } else { + this.updateStatus(`Benchmark Loop Upload nr.:` + this.networkStatsUpload.length); + } + const myConfig = this.ds.checkConfig$.getValue(); + const benchmarkDefinition = (isDownloadPart) ? myConfig.downloadspeed : myConfig.uploadspeed; return new Promise((resolve, reject) => { - this.benchmarkSequence(type) + this.benchmarkSequence(isDownloadPart) .then(results => { const averageBytesPerSecond = NetworkCheckComponent.calculateAverageSpeedBytePerSecond(results); - const averageOfPreviousLoops = this.getAverageNetworkStat(type); - console.log({type: `${type}`, results: results, avg: averageBytesPerSecond}); + const averageOfPreviousLoops = this.getAverageNetworkStat(isDownloadPart); + + console.log({type: isDownloadPart ? 'download' : 'upload', results: results, avg: averageBytesPerSecond}); const errors = results.reduce((a, r) => a + ((r.error !== null) ? 1 : 0), 0); - this.networkStats.get(type).push(averageBytesPerSecond); - this.showBenchmarkSequenceResults(type, this.getAverageNetworkStat(type), results); + let statsLength; + if (isDownloadPart) { + this.networkStatsDownload.push(averageBytesPerSecond); + statsLength = this.networkStatsDownload.length + } else { + this.networkStatsUpload.push(averageBytesPerSecond); + statsLength = this.networkStatsUpload.length + } + this.showBenchmarkSequenceResults(isDownloadPart, this.getAverageNetworkStat(isDownloadPart), results); - if (errors > benchmarkDefinition.allowedErrorsPerSequence) { + if (errors > benchmarkDefinition.maxErrorsPerSequence) { console.warn('some errors occurred', results); return reject(errors); } - if (this.networkStats.get(type).length > benchmarkDefinition.allowedSequenceRepetitions) { - console.warn(`looped ${benchmarkDefinition.allowedSequenceRepetitions} times, but could not get reliable average`); + if (statsLength > benchmarkDefinition.maxSequenceRepetitions) { + console.warn(`looped ${benchmarkDefinition.maxSequenceRepetitions} times, but could not get reliable average`); return resolve(); } + console.log("mmmmmm " + statsLength.toString()); if ( - (this.networkStats.get(type).length < 3) || - (Math.abs(averageOfPreviousLoops - averageBytesPerSecond) > benchmarkDefinition.allowedDevianceBytesPerSecond) + (statsLength < 3) || + (Math.abs(averageOfPreviousLoops - averageBytesPerSecond) > benchmarkDefinition.maxDevianceBytesPerSecond) ) { - return this.loopBenchmarkSequence(type).then(resolve).catch(reject); + return this.loopBenchmarkSequence(isDownloadPart).then(resolve).catch(reject); } resolve(); @@ -208,17 +167,18 @@ export class NetworkCheckComponent implements OnInit { }); } - - private getAverageNetworkStat(type: BenchmarkType): number { - - return this.networkStats.get(type).reduce((a, x) => a + x, 0) / this.networkStats.get(type).length; + private getAverageNetworkStat(isDownloadPart: boolean): number { + return (isDownloadPart) ? + (this.networkStatsDownload.reduce((a, x) => a + x, 0) / this.networkStatsDownload.length) : + (this.networkStatsUpload.reduce((a, x) => a + x, 0) / this.networkStatsUpload.length); } + private benchmarkSequence(isDownloadPart: boolean): Promise<Array<NetworkRequestTestResult>> { + const myConfig = this.ds.checkConfig$.getValue(); + const benchmarkDefinition = (isDownloadPart) ? myConfig.downloadspeed : myConfig.uploadspeed; - private benchmarkSequence(type: BenchmarkType): Promise<Array<NetworkRequestTestResult>> { - - return this.benchmarkDefinitions.get(type).testSizes.reduce( - (sequence, testSize) => sequence.then(results => this.benchmark(type, testSize) + return benchmarkDefinition.sequenceSizes.reduce( + (sequence, testSize) => sequence.then(results => this.benchmark(isDownloadPart, testSize) .then(result => { results.push(result); return results; @@ -229,12 +189,12 @@ export class NetworkCheckComponent implements OnInit { } - private benchmark(benchmarkType: BenchmarkType, requestSize: number): Promise<NetworkRequestTestResult> { + private benchmark(isDownloadPart: boolean, requestSize: number): Promise<NetworkRequestTestResult> { // console.log(`run benchmark ${benchmarkType} for ${requestSize}`); - const testRound = this.networkStats.get(benchmarkType).length + 1; + const testRound = (isDownloadPart) ? (this.networkStatsDownload.length + 1) : (this.networkStatsUpload.length + 1); const testPackage = this.humanReadableBytes(requestSize); - if (benchmarkType === BenchmarkType.down) { + if (isDownloadPart) { this.updateStatus(`Downloadgeschwindigkeit Testrunde ${testRound} - Testgröße: ${testPackage} bytes`); return this.bs.benchmarkDownloadRequest(requestSize); } else { @@ -244,30 +204,27 @@ export class NetworkCheckComponent implements OnInit { } - private showBenchmarkSequenceResults(type: BenchmarkType, avgBytesPerSecond: number, results: Array<NetworkRequestTestResult> = []) { + private showBenchmarkSequenceResults(isDownloadPart: boolean, avgBytesPerSecond: number, results: Array<NetworkRequestTestResult> = []) { - if (type === BenchmarkType.down) { + if (isDownloadPart) { this.status.avgDownloadSpeedBytesPerSecond = avgBytesPerSecond; - } - if (type === BenchmarkType.up) { + } else { this.status.avgUploadSpeedBytesPerSecond = avgBytesPerSecond; } - this.plotStatistics(type, results); + this.plotStatistics(isDownloadPart, results); } - private plotStatistics(benchmarkType: BenchmarkType, benchmarkSequenceResults: Array<NetworkRequestTestResult>) { + private plotStatistics(isDownloadPart: boolean, benchmarkSequenceResults: Array<NetworkRequestTestResult>) { const datapoints = benchmarkSequenceResults .filter(measurement => (measurement.error === null)) .map(measurement => ([measurement.size, measurement.duration])); - if (benchmarkType === BenchmarkType.down) { + if (isDownloadPart) { this.downloadPlotter.plotData(datapoints, null, 'dots'); - } - - if (benchmarkType === BenchmarkType.up) { + } else { this.uploadPlotter.plotData(datapoints, null, 'dots'); } @@ -289,8 +246,8 @@ export class NetworkCheckComponent implements OnInit { this.updateStatus(`Die folgenden Netzwerkeigenschaften wurden festgestellt:`); this.status.done = true; - const downAvg = this.getAverageNetworkStat(BenchmarkType.down); - const upAvg = this.getAverageNetworkStat(BenchmarkType.up); + const downAvg = this.getAverageNetworkStat(true); + const upAvg = this.getAverageNetworkStat(false); const testConfig = this.ds.checkConfig$.getValue(); @@ -306,10 +263,10 @@ export class NetworkCheckComponent implements OnInit { }; reportEntry('Downloadgeschwindigkeit', this.humanReadableBytes(downAvg, true) + '/s'); - reportEntry('Downloadgeschwindigkeit benötigt', this.humanReadableBytes(testConfig.downloadMinimum, true) + '/s'); + reportEntry('Downloadgeschwindigkeit benötigt', this.humanReadableBytes(testConfig.downloadspeed.min, true) + '/s'); reportEntry('Downloadbewertung', this.networkRating.downloadRating, this.networkRating.downloadRating === 'insufficient'); reportEntry('Uploadgeschwindigkeit', this.humanReadableBytes(upAvg, true) + '/s'); - reportEntry('Uploadgeschwindigkeit benötigt', this.humanReadableBytes(testConfig.uploadMinimum, true) + '/s'); + reportEntry('Uploadgeschwindigkeit benötigt', this.humanReadableBytes(testConfig.uploadspeed.min, true) + '/s'); reportEntry('Uploadbewertung', this.networkRating.uploadRating, this.networkRating.uploadRating === 'insufficient'); reportEntry('Gesamtbewertung', this.networkRating.overallRating, this.networkRating.overallRating === 'insufficient'); @@ -336,26 +293,26 @@ export class NetworkCheckComponent implements OnInit { }; const nd = { - avgDownloadSpeed: this.getAverageNetworkStat(BenchmarkType.down), - avgUploadSpeed: this.getAverageNetworkStat(BenchmarkType.up), + avgDownloadSpeed: this.getAverageNetworkStat(true), + avgUploadSpeed: this.getAverageNetworkStat(false), }; console.log('measured averages', nd); // the ratings are calculated individually, by a "how low can you go" approach awardedNetworkRating.downloadRating = 'good'; - if (nd.avgDownloadSpeed < testConfig.downloadGood) { + if (nd.avgDownloadSpeed < testConfig.downloadspeed.good) { awardedNetworkRating.downloadRating = 'ok'; } - if (nd.avgDownloadSpeed < testConfig.downloadMinimum) { + if (nd.avgDownloadSpeed < testConfig.downloadspeed.min) { awardedNetworkRating.downloadRating = 'insufficient'; } awardedNetworkRating.uploadRating = 'good'; - if (nd.avgUploadSpeed < testConfig.uploadGood) { + if (nd.avgUploadSpeed < testConfig.uploadspeed.good) { awardedNetworkRating.uploadRating = 'ok'; } - if (nd.avgUploadSpeed < testConfig.uploadMinimum) { + if (nd.avgUploadSpeed < testConfig.uploadspeed.min) { awardedNetworkRating.uploadRating = 'insufficient'; } diff --git a/src/app/sys-check/network-check/tc-speed-chart.component.ts b/src/app/sys-check/network-check/tc-speed-chart.component.ts index a7fcf865790e49d6349cabe3fb1e2a049eafdcad..c45044705c3e01228bf4aa78f5e388ec279a49b4 100644 --- a/src/app/sys-check/network-check/tc-speed-chart.component.ts +++ b/src/app/sys-check/network-check/tc-speed-chart.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, OnInit } from '@angular/core'; +import { Component, ElementRef } from '@angular/core'; export interface TcSpeedChartSettings { lineWidth: number; @@ -37,14 +37,14 @@ export class TcSpeedChartComponent { css: 'border: 1px solid black', lineWidth: 5, width: 800, - height: 240, + height: 400, gridColor: 'silver', axisColor: 'red', labelFont: '20 pt Verdana', labelPadding: 4, xAxisMaxValue: 200, xAxisMinValue: -10, - yAxisMaxValue: 100, + yAxisMaxValue: 300, yAxisMinValue: -10, xAxisStepSize: 20, yAxisStepSize: 10, @@ -64,7 +64,6 @@ export class TcSpeedChartComponent { this.context = this.canvas.getContext('2d'); this.config = {...this.config, ...config}; - this.canvas.setAttribute('style', this.config.css); this.canvas.setAttribute('height', this.config.height); this.canvas.setAttribute('width', this.config.width); diff --git a/src/app/sys-check/questionnaire/questionnaire.component.html b/src/app/sys-check/questionnaire/questionnaire.component.html index b4b805af60356554fd45cadc2e42d316de66a12d..e227530c33319f59133b13633b6200c02acbe3c3 100644 --- a/src/app/sys-check/questionnaire/questionnaire.component.html +++ b/src/app/sys-check/questionnaire/questionnaire.component.html @@ -1,49 +1,45 @@ -<mat-card> +<mat-card-header> + <mat-card-title>Fragen</mat-card-title> + <mat-card-subtitle>Bitte bearbeiten Sie die nachfolgenden Fragen.</mat-card-subtitle> +</mat-card-header> - <mat-card-header> - <mat-card-title>Fragen</mat-card-title> - <mat-card-subtitle>Bitte füllen Sie die folgenden Fragen aus</mat-card-subtitle> - </mat-card-header> - - <mat-card-content style="height: 610px; overflow: auto"> - <div #questionnaireBody> - <div [formGroup]="form" class="formList" fxLayout="column"> - <div *ngFor="let q of questions"> - <div [ngSwitch]="q.type" class="formEntry" fxLayout="column" fxLayoutGap="5px"> - <h3 *ngSwitchCase="'header'">{{ q.prompt }}{{ q.value }}</h3> - <mat-form-field *ngSwitchCase="'text'"> - <p>{{q.prompt}}</p> - <textarea matInput [formControlName]="q.id" [id]="q.id" cdkTextareaAutosize cdkAutosizeMinRows="2" class="formEntry"></textarea> - </mat-form-field> - <mat-form-field *ngSwitchCase="'string'"> - <p>{{q.prompt}}</p> - <input matInput [formControlName]="q.id" [id]="q.id" class="formEntry"> - </mat-form-field> - <mat-form-field *ngSwitchCase="'select'"> - <p>{{q.prompt}}</p> - <mat-select [id]="q.id" [formControlName]="q.id" class="formEntry"> - <mat-option *ngFor="let opt of q.options" [value]="opt"> - {{opt}} - </mat-option> - </mat-select> - </mat-form-field> - <div *ngSwitchCase="'check'"> - <p *ngIf="q.prompt.length > 0">{{q.prompt}}</p> - <mat-checkbox *ngSwitchCase="'check'" [formControlName]="q.id" [id]="q.id">{{q.value}}</mat-checkbox> - </div> - <div *ngSwitchCase="'radio'"> - <p>{{q.prompt}}</p> - <mat-radio-group [id]="q.id" [formControlName]="q.id" [name]="q.id"> - <mat-radio-button *ngFor="let opt of q.options" [value]="opt" class="formEntry"> - {{opt}} - </mat-radio-button> - </mat-radio-group> - </div> - <p *ngSwitchDefault>Unbekannter Control-Typ: {{q.type}} für Element-ID {{q.id}}</p> +<mat-card-content style="height: 610px; overflow: auto"> + <div #questionnaireBody> + <div [formGroup]="form" class="formList" fxLayout="column"> + <div *ngFor="let q of questions"> + <div [ngSwitch]="q.type" class="formEntry" fxLayout="column" fxLayoutGap="5px"> + <h3 *ngSwitchCase="'header'">{{ q.prompt }}{{ q.value }}</h3> + <mat-form-field *ngSwitchCase="'text'"> + <p>{{q.prompt}}</p> + <textarea matInput [formControlName]="q.id" [id]="q.id" cdkTextareaAutosize cdkAutosizeMinRows="2" class="formEntry"></textarea> + </mat-form-field> + <mat-form-field *ngSwitchCase="'string'"> + <p>{{q.prompt}}</p> + <input matInput [formControlName]="q.id" [id]="q.id" class="formEntry"> + </mat-form-field> + <mat-form-field *ngSwitchCase="'select'"> + <p>{{q.prompt}}</p> + <mat-select [id]="q.id" [formControlName]="q.id" class="formEntry"> + <mat-option *ngFor="let opt of q.options" [value]="opt"> + {{opt}} + </mat-option> + </mat-select> + </mat-form-field> + <div *ngSwitchCase="'check'"> + <p *ngIf="q.prompt.length > 0">{{q.prompt}}</p> + <mat-checkbox *ngSwitchCase="'check'" [formControlName]="q.id" [id]="q.id">{{q.value}}</mat-checkbox> + </div> + <div *ngSwitchCase="'radio'"> + <p>{{q.prompt}}</p> + <mat-radio-group [id]="q.id" [formControlName]="q.id" [name]="q.id"> + <mat-radio-button *ngFor="let opt of q.options" [value]="opt" class="formEntry"> + {{opt}} + </mat-radio-button> + </mat-radio-group> </div> + <p *ngSwitchDefault>Unbekannter Control-Typ: {{q.type}} für Element-ID {{q.id}}</p> </div> </div> </div> - </mat-card-content> - -</mat-card> + </div> +</mat-card-content> diff --git a/src/app/sys-check/questionnaire/questionnaire.component.ts b/src/app/sys-check/questionnaire/questionnaire.component.ts index 1913f4f3e3e047cba92922dd099134d1cea2863a..a93306bcd76b5681ffa8a213d05e215b86746f96 100644 --- a/src/app/sys-check/questionnaire/questionnaire.component.ts +++ b/src/app/sys-check/questionnaire/questionnaire.component.ts @@ -1,7 +1,7 @@ import { FormControl, FormGroup } from '@angular/forms'; -import { FormDefEntry, ReportEntry } from '../backend.service'; import { SyscheckDataService } from '../syscheck-data.service'; import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; +import { FormDefEntry, ReportEntry } from '../sys-check.interfaces'; @Component({ selector: 'iqb-questionnaire', @@ -34,7 +34,7 @@ export class QuestionnaireComponent implements OnInit { group[question.id] = new FormControl(''); }); this.form = new FormGroup(group); - this.form.valueChanges.subscribe(f => {this.updateReport(); }); + this.form.valueChanges.subscribe(() => {this.updateReport(); }); this.updateReport(); } }); @@ -46,7 +46,7 @@ export class QuestionnaireComponent implements OnInit { this.questions.forEach(element => { if (element.type !== 'header') { const value = this.form.controls[element.id].value; - const warning = (['string', 'select', 'radio'].indexOf(element.type) > -1) && (value === ''); + const warning = (['string', 'select', 'radio', 'text'].indexOf(element.type) > -1) && (value === '') && (element.required); myReportEntries.push({'id': element.id, 'type': element.type, 'label': element.prompt, 'value': value, warning: warning}); } }); diff --git a/src/app/sys-check/report/report.component.html b/src/app/sys-check/report/report.component.html index f9197aad686b5bfe0e559533a159b9904ca7cdae..dc2a7a021e51e72c1b2b042fd5edf2cdd901aed7 100644 --- a/src/app/sys-check/report/report.component.html +++ b/src/app/sys-check/report/report.component.html @@ -1,80 +1,23 @@ - -<mat-card> - - <mat-card-header> - <mat-card-title>Bericht</mat-card-title> - <mat-card-subtitle>Folgende Informationen über Ihr System wurden gesammelt</mat-card-subtitle> - </mat-card-header> - - <mat-card-content id="report-cointainer" fxLayout.gt-md="row" fxLayout="column"> - <table fxFlex.gt-md="25%" fxFlex="100%"> - <thead> - <tr> - <td colspan="2">Computer-Info</td> - </tr> - </thead> - <tbody> - <tr *ngFor="let rd of environmentData" [ngStyle]="{color: (rd.warning ? 'red' : 'black')}"> - <td>{{rd.label}}</td> - <td>{{rd.value}}</td> - </tr> - </tbody> - </table> - - <table *ngIf="networkData.length > 0" fxFlex.gt-md="25%" fxFlex="100%"> - <thead> - <tr> - <td colspan="2">Netzwerk-Info</td> - </tr> - </thead> - <tbody> - <tr *ngFor="let rd of networkData" [ngStyle]="{color: (rd.warning ? 'red' : 'black')}"> - <td>{{rd.label}}</td> - <td>{{rd.value}}</td> - </tr> - </tbody> - </table> - - <table *ngIf="questionnaireData.length > 0" fxFlex.gt-md="25%" fxFlex="100%"> - <thead> - <tr> - <td colspan="2">Fragen</td> - </tr> - </thead> - <tbody> - <tr *ngFor="let rd of questionnaireData" [ngStyle]="{color: (rd.warning ? 'red' : 'black')}"> - <td>{{rd.label}}</td> - <td>{{rd.value}}</td> - </tr> - </tbody> - </table> - - <table *ngIf="unitData.length > 0" fxFlex.gt-md="25%" fxFlex="100%"> - <thead> - <tr> - <td colspan="2">Test-Unit</td> - </tr> - </thead> - <tbody> - <tr *ngFor="let rd of unitData" [ngStyle]="{color: (rd.warning ? 'red' : 'black')}"> - <td>{{rd.label}}</td> - <td>{{rd.value}}</td> - </tr> - </tbody> - </table> - </mat-card-content> - - <label *ngIf="csvReport">Export als CSV für Copy&Paste - <textarea style="width:100%; height: 8em">{{csvReport}}</textarea> - </label> - - <mat-card-actions> - <button *ngIf="canSave && isReady()" mat-raised-button color="primary" (click)="saveReport()" class="save_button">Senden</button> +<mat-card-header> + <mat-card-title>Bericht</mat-card-title> + <mat-card-subtitle *ngIf="canSave">Dieser System-Check wurde so konfiguriert, dass nach Abschluss ein Bericht geschickt werden kann. Hierfür ist ein Kennwort erforderlich.</mat-card-subtitle> + <mat-card-subtitle *ngIf="!canSave">Für diesen System-Check ist nicht vorgesehen, dass nach Abschluss ein Bericht geschickt wird.</mat-card-subtitle> +</mat-card-header> + +<mat-card-content id="report-cointainer" fxLayout.gt-md="row" fxLayout="column" *ngIf="canSave"> + <p *ngIf="questionnaireDataWarnings.length > 0">Bitte prüfen Sie die Eingaben:</p> + <ul> + <li *ngFor="let qd of questionnaireDataWarnings"> + {{qd.label}} + </li> + </ul> +</mat-card-content> + +<label *ngIf="csvReport">Export als CSV für Copy&Paste + <textarea style="width:100%; height: 8em">{{csvReport}}</textarea> +</label> + +<mat-card-actions> + <button [disabled]="!isReady() || !canSave" mat-raised-button color="primary" (click)="saveReport()" class="save_button">Bericht senden</button> <!-- <button *ngIf="isReady()" mat-raised-button (click)="exportReport()" class="save_button">Als CSV zum kopieren</button>--> - </mat-card-actions> - -</mat-card> - - - - +</mat-card-actions> diff --git a/src/app/sys-check/report/report.component.ts b/src/app/sys-check/report/report.component.ts index 7ec6934250dbb359136a1b51742ad861f47a958f..dbbdb5185cb0dbd90be4ee7f83f78a89529a47e6 100644 --- a/src/app/sys-check/report/report.component.ts +++ b/src/app/sys-check/report/report.component.ts @@ -1,8 +1,10 @@ -import {BackendService, ReportEntry} from '../backend.service'; +import { BackendService } from '../backend.service'; import { SyscheckDataService } from '../syscheck-data.service'; -import {Component, Input} from '@angular/core'; -import {SaveReportComponent} from './save-report/save-report.component'; -import {MatDialog, MatSnackBar} from '@angular/material'; +import { Component, Input } from '@angular/core'; +import { SaveReportComponent } from './save-report/save-report.component'; +import { MatDialog, MatSnackBar } from '@angular/material'; +import { ReportEntry } from '../sys-check.interfaces'; +import { Subscription } from 'rxjs'; @Component({ selector: 'iqb-report', @@ -16,20 +18,34 @@ export class ReportComponent { environmentData: ReportEntry[] = []; networkData: ReportEntry[] = []; questionnaireData: ReportEntry[] = []; + questionnaireDataWarnings: ReportEntry[] = []; unitData: ReportEntry[] = []; csvReport = ''; + private eDataSubscription: Subscription; + private nDataSubscription: Subscription; + private qDataSubscription: Subscription; + private uDataSubscription: Subscription; + constructor( private bs: BackendService, private ds: SyscheckDataService, private saveDialog: MatDialog, private snackBar: MatSnackBar ) { - this.ds.environmentData$.subscribe(rd => {this.environmentData = rd; }); - this.ds.networkData$.subscribe(rd => {this.networkData = rd; }); - this.ds.questionnaireData$.subscribe(rd => this.questionnaireData = rd); - this.ds.unitData$.subscribe(rd => this.unitData = rd); + this.eDataSubscription = this.ds.environmentData$.subscribe(rd => {this.environmentData = rd; }); + this.nDataSubscription = this.ds.networkData$.subscribe(rd => {this.networkData = rd; }); + this.qDataSubscription = this.ds.questionnaireData$.subscribe(rd => { + this.questionnaireData = rd; + this.questionnaireDataWarnings = []; + this.questionnaireData.forEach(re => { + if (re.warning) { + this.questionnaireDataWarnings.push(re); + } + }); + }); + this.uDataSubscription = this.ds.unitData$.subscribe(rd => this.unitData = rd); } saveReport() { @@ -75,4 +91,19 @@ export class ReportComponent { isReady() { return (typeof this.ds.task$.getValue() === 'undefined') && !this.ds.taskQueue.length; } + + ngOnDestroy() { + if (this.eDataSubscription) { + this.eDataSubscription.unsubscribe(); + } + if (this.nDataSubscription) { + this.nDataSubscription.unsubscribe(); + } + if (this.qDataSubscription) { + this.qDataSubscription.unsubscribe(); + } + if (this.uDataSubscription) { + this.uDataSubscription.unsubscribe(); + } + } } diff --git a/src/app/sys-check/start.component.html b/src/app/sys-check/start.component.html index b9a21e449cb9c06aaa40b9c179ff05af284593e2..718873a8f0f840eab73356b9f86fe9327879c410 100644 --- a/src/app/sys-check/start.component.html +++ b/src/app/sys-check/start.component.html @@ -1,6 +1,6 @@ <div class="logo"> <a [routerLink]="['/']"> - <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite" alt="IQB-logo"/> </a> </div> <div class="spinner-container" *ngIf="dataLoading"> @@ -9,21 +9,23 @@ <div class="page-body"> <div fxLayout="row" fxLayoutAlign="center start"> <mat-card fxFlex="0 2 500px"> - <mat-card-title>Starten</mat-card-title> + <mat-card-title>System-Check: Starten</mat-card-title> <mat-card-content> <p>Hier können Sie ermitteln, ob das Computersystem, das Sie gerade benutzen, für - die hier vorgesehenen Testungen geeignet ist. Sie erhalten hierzu einen detaillierten Bericht, - den Sie speichern oder drucken können.</p> - <p *ngIf="checkConfigList.length === 0"> + die hier vorgesehenen Testungen geeignet ist.</p> + <p *ngIf="dataLoading"> Bitte warten... Konfiguration wird geladen </p> - <p *ngIf="checkConfigList.length > 1"> + <p *ngIf="!dataLoading && (checkConfigList.length === 0)"> + Auf diesem Server ist aktuell kein System-Check verfügbar. + </p> + <p *ngIf="!dataLoading && (checkConfigList.length > 1)"> Bitte wählen Sie einen Check aus! </p> - <p *ngIf="checkConfigList.length === 1"> + <p *ngIf="!dataLoading && (checkConfigList.length === 1)"> Bitte klicken Sie auf den Schalter, um den Check zu starten! </p> - <div fxLayout="row" fxLayoutGap="10px" fxLayout="column"> + <div fxLayout="column" fxLayoutGap="10px"> <button mat-raised-button color="primary" (click)="buttonStartCheck(c)" *ngFor="let c of checkConfigList"> <div fxLayout="column"> @@ -33,6 +35,9 @@ </button> </div> </mat-card-content> + <mat-card-actions> + <button (click)="goBack()" mat-raised-button color="primary"><i class="material-icons">arrow_back</i> zurück zur Startseite</button> + </mat-card-actions> </mat-card> </div> </div> diff --git a/src/app/sys-check/start.component.ts b/src/app/sys-check/start.component.ts index b0bd20c4d9df0c2ef75f75998074d65086d3bf08..fd7e0024ec7270b6416c11508bd8bf45f3dc99e2 100644 --- a/src/app/sys-check/start.component.ts +++ b/src/app/sys-check/start.component.ts @@ -1,7 +1,8 @@ import { SyscheckDataService } from './syscheck-data.service'; import { Router, ActivatedRoute } from '@angular/router'; -import { BackendService, CheckConfig } from './backend.service'; +import { BackendService } from './backend.service'; import { Component, OnInit } from '@angular/core'; +import { CheckConfig } from './sys-check.interfaces'; @@ -24,13 +25,15 @@ export class StartComponent implements OnInit { this.dataLoading = true; this.bs.getCheckConfigs().subscribe(myConfigs => { this.checkConfigList = myConfigs; - this.checkConfigList.push(this.bs.basicTestConfig); this.dataLoading = false; }); } - // # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # buttonStartCheck(c: CheckConfig) { this.router.navigate(['../run/' + c.id], {relativeTo: this.route}); } + + goBack() { + this.router.navigate(['/']); + } } diff --git a/src/app/sys-check/sys-check.component.html b/src/app/sys-check/sys-check.component.html index 13ed2b95a2b2a780bdf33dd2e99a5706b778b481..1f86c79bc078cf54b3061de48ef0be36957bc696 100644 --- a/src/app/sys-check/sys-check.component.html +++ b/src/app/sys-check/sys-check.component.html @@ -1,25 +1,35 @@ <div class="logo"> <a [routerLink]="['/']"> - <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/> + <img src="assets/IQB-LogoA.png" matTooltip="Startseite" alt="iqb-logo"/> </a> </div> -<div class="pagetitle">{{title}}</div> +<div class="pagetitle">System-Check: {{title}}</div> <div class="page-body"> - - <iqb-network-check [measureNetwork]="checks.network" [ngClass]="{toprow: checks.network}"></iqb-network-check> - - <div fxLayout="column" fxLayout.gt-md="row" class="cardbox" [ngClass]="{toprow: !checks.network}"> - - <iqb-unit-check fxFlex="100%" [fxFlex.gt-md]="(checks.unit && checks.questions) ? '75%' : '100%'" *ngIf="checks.unit"></iqb-unit-check> - - <iqb-questionnaire fxFlex="100%" [fxFlex.gt-md]="(checks.unit && checks.questions) ? '25%' : '100%'" *ngIf="checks.questions"></iqb-questionnaire> - + <div class="spinner-container-local" *ngIf="dataLoading"> + <mat-spinner></mat-spinner> + </div> + <div fxLayout="column" fxLayoutAlign="center stretch" class="cardhost"> + <div fxLayout="raw wrap" fxLayoutAlign="center stretch" fxFlex> + <mat-card fxFlex> + <iqb-environment-check></iqb-environment-check> + </mat-card> + <mat-card fxFlex="2 0 800px"> + <iqb-network-check [measureNetwork]="checks.network"></iqb-network-check> + </mat-card> + </div> + <div fxLayout="row" fxLayoutAlign="center stretch"> + <mat-card [fxFlex]="(checks.questions) ? '2 0 800px' : ''" *ngIf="checks.unit"> + <iqb-unit-check></iqb-unit-check> + </mat-card> + <mat-card fxFlex *ngIf="checks.questions"> + <iqb-questionnaire></iqb-questionnaire> + </mat-card> + </div> + <mat-card> + <iqb-report [canSave]="checks.report"></iqb-report> + </mat-card> </div> - - <iqb-environment-check *ngIf="checks.environment"></iqb-environment-check> - - <iqb-report [canSave]="checks.report"></iqb-report> </div> diff --git a/src/app/sys-check/sys-check.component.scss b/src/app/sys-check/sys-check.component.scss index 6df33bc6663d334304669b0602e4c6263f359673..8c5e8094d2e692970668c9f557164fa2063aad84 100644 --- a/src/app/sys-check/sys-check.component.scss +++ b/src/app/sys-check/sys-check.component.scss @@ -8,32 +8,14 @@ height: 100%; } -::ng-deep mat-card { - margin: 1em; +mat-card { + margin: 10px; } -::ng-deep .mat-card-header-text { - margin-left: 0 !important; +.cardhost { + margin: 10px; } -$mat-gt-md: "screen and (min-width: 1280px)"; -@media #{$mat-gt-md} { - - .cardbox { - margin: 1em - } - - ::ng-deep .cardbox mat-card { - margin: 0 1em 0 0 - } - - ::ng-deep .cardbox :last-child mat-card { - margin: 0 - } - - ::ng-deep .toprow, - ::ng-deep .toprow mat-card { - margin-top: 0 !important - } - +::ng-deep .mat-card-header-text { + margin-left: 0 !important; } diff --git a/src/app/sys-check/sys-check.component.ts b/src/app/sys-check/sys-check.component.ts index d90e466457d438e8d44eab38673f19e6d5a069f6..44231ee234717e174bf485614e91d067baac443c 100644 --- a/src/app/sys-check/sys-check.component.ts +++ b/src/app/sys-check/sys-check.component.ts @@ -1,7 +1,8 @@ import { SyscheckDataService } from './syscheck-data.service'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { Component, OnInit} from '@angular/core'; -import {BackendService, CheckConfigData, FormDefEntry, Rating} from './backend.service'; +import { BackendService } from './backend.service'; +import { Subscription } from 'rxjs'; interface Checks { @@ -18,6 +19,8 @@ interface Checks { styleUrls: ['./sys-check.component.scss'] }) export class SysCheckComponent implements OnInit { + private taskSubscription: Subscription = null; + dataLoading = false; checks: Checks = { environment: true, @@ -37,41 +40,35 @@ export class SysCheckComponent implements OnInit { } ngOnInit() { - this.route.paramMap.subscribe((params: ParamMap) => { const paramId = params.get('c'); - if (paramId === this.bs.basicTestConfig.id) { - this.loadTestConfig(this.bs.basicTestConfigData); - } else { - this.bs.getCheckConfigData(paramId).subscribe(config => this.loadTestConfig(config)); - } - }); - } + this.bs.getCheckConfigData(paramId).subscribe(checkConfig => { + this.ds.checkConfig$.next(checkConfig); - loadTestConfig(checkConfig: CheckConfigData) { + this.title = checkConfig.label; + this.checks.unit = checkConfig.hasunit; + this.checks.network = !checkConfig.skipnetwork; + this.checks.questions = checkConfig.questions.length > 0; + this.checks.report = checkConfig.cansave; - this.title = checkConfig.label; - this.checks.environment = !checkConfig.questionsonlymode; - this.checks.unit = checkConfig.hasunit && !checkConfig.questionsonlymode; - this.checks.network = !checkConfig.skipnetwork && !checkConfig.questionsonlymode; - this.checks.questions = checkConfig.questions.length > 0; - this.checks.report = checkConfig.cansave; + if (this.checks.unit) { + this.ds.taskQueue.push('loadunit'); + } + if (this.checks.network) { + this.ds.taskQueue.push('speedtest'); + } + this.ds.nextTask(); + this.taskSubscription = this.ds.task$.subscribe(task => { + this.dataLoading = (typeof task !== 'undefined') && (this.ds.taskQueue.length > 0); + }); - (checkConfig.ratings || []).forEach(rating => { - if (rating.type === 'download') { - checkConfig.downloadGood = rating.good; - checkConfig.downloadMinimum = rating.min; - } - if (rating.type === 'upload') { - checkConfig.uploadGood = rating.good; - checkConfig.uploadMinimum = rating.min; - } + }); }); + } - this.ds.checkConfig$.next(checkConfig); - - if (this.checks.unit) { this.ds.taskQueue.push('loadunit'); } - if (this.checks.network) { this.ds.taskQueue.push('speedtest'); } - this.ds.nextTask(); + ngOnDestroy() { + if (this.taskSubscription !== null) { + this.taskSubscription.unsubscribe(); + } } } diff --git a/src/app/sys-check/sys-check.interfaces.ts b/src/app/sys-check/sys-check.interfaces.ts new file mode 100644 index 0000000000000000000000000000000000000000..e58c45220b21a61e60faea582aced8b2a301012f --- /dev/null +++ b/src/app/sys-check/sys-check.interfaces.ts @@ -0,0 +1,88 @@ +export interface CheckConfig { + id: string; + label: string; + description: string; +} + +export interface SpeedParameters { + min: number; + good: number; + maxDevianceBytesPerSecond: number; + maxErrorsPerSequence: number; + maxSequenceRepetitions: number; + sequenceSizes: number[]; +} + +export interface CheckConfigData { + id: string; + label: string; + questions: FormDefEntry[]; + hasunit: boolean; + cansave: boolean; + customtexts: CustomText[]; + skipnetwork: boolean; + downloadspeed: SpeedParameters; + uploadspeed: SpeedParameters; +} + +export interface FormDefEntry { + id: string; + type: string; + prompt: string; + value: string; + options: string[]; + required: boolean; +} + +export interface CustomText { + key: string; + value: string; +} + +export interface UnitData { + key: string; + label: string; + def: string; + player: string; + player_id: string; + duration: number; +} + +export interface NetworkRequestTestResult { + 'type': 'downloadTest' | 'uploadTest'; + 'size': number; + 'duration': number; + 'error': string | null; + 'speedInBPS': number; +} + +export interface ReportEntry { + id: string; + type: string; + label: string; + value: string; + warning: boolean; +} + +export interface NetworkCheckStatus { + message: string; + avgUploadSpeedBytesPerSecond: number; + avgDownloadSpeedBytesPerSecond: number; + done: boolean; +} + +export type TechCheckRating = 'N/A' | 'insufficient' | 'ok' | 'good' | 'unstable'; + +export interface NetworkRating { + uploadRating: TechCheckRating; + downloadRating: TechCheckRating; + overallRating: TechCheckRating; +} + +export interface DetectedNetworkInformation { + available: boolean; + downlinkMegabitPerSecond: number; + effectiveNetworkType: string; + roundTripTimeMs: number; + networkType: string; +} diff --git a/src/app/sys-check/sys-check.module.ts b/src/app/sys-check/sys-check.module.ts index 63fe80ca0b69d959347a8c230b92fc0d0d8fa6b0..28a695f20e9a24714d81b40600115447bcf0de78 100644 --- a/src/app/sys-check/sys-check.module.ts +++ b/src/app/sys-check/sys-check.module.ts @@ -24,6 +24,7 @@ import { SaveReportComponent } from './report/save-report/save-report.component' import { UnitNaviButtonsComponent } from './unit-check/tc-navi-buttons/unit-navi-buttons.component'; import { TcSpeedChartComponent } from './network-check/tc-speed-chart.component'; +import { MatTooltipModule } from '@angular/material/tooltip'; @NgModule({ imports: [ @@ -42,6 +43,7 @@ import { TcSpeedChartComponent } from './network-check/tc-speed-chart.component' MatIconModule, MatSelectModule, MatRadioModule, + MatTooltipModule, MatSnackBarModule, MatDialogModule, ReactiveFormsModule diff --git a/src/app/sys-check/syscheck-data.service.ts b/src/app/sys-check/syscheck-data.service.ts index d3478b2436ebf1210fa0ba3c7ecafc4f5744393d..83142a13d3c33907caeb4a6b0810150f46c51bc7 100644 --- a/src/app/sys-check/syscheck-data.service.ts +++ b/src/app/sys-check/syscheck-data.service.ts @@ -1,6 +1,6 @@ -import { CheckConfigData, ReportEntry } from './backend.service'; import { BehaviorSubject } from 'rxjs'; import { Injectable } from '@angular/core'; +import { CheckConfigData, ReportEntry } from './sys-check.interfaces'; type Task = 'loadunit' | 'speedtest' | null; @@ -9,7 +9,33 @@ type Task = 'loadunit' | 'speedtest' | null; }) export class SyscheckDataService { - public checkConfig$ = new BehaviorSubject<CheckConfigData>(null); + public checkConfig$ = new BehaviorSubject<CheckConfigData>( + { + id: 'Basistest', + label: 'Basistest', + questions: [], + hasunit: false, + cansave: false, + customtexts: [], + skipnetwork: false, + downloadspeed : { + min: 1.875e+6, // 15Mbit/s ~> typical dl speed 4G CAT4 + good: 3.75e+6, // 30Mbit/s ~> typical dl speed 4G+ CAT6 + maxDevianceBytesPerSecond: 100000, + maxErrorsPerSequence: 0, + maxSequenceRepetitions: 15, + sequenceSizes: [400000, 800000, 1600000, 3200000] + }, + uploadspeed : { + min: 250000, // 2Mbit/s + good: 1.25e+6, // 10Mbit/s + maxDevianceBytesPerSecond: 5000, + maxErrorsPerSequence: 0, + maxSequenceRepetitions: 15, + sequenceSizes: [100000, 200000, 400000, 800000] + } + } + ); public task$ = new BehaviorSubject<Task>(null); public taskQueue: Task[]; @@ -26,7 +52,7 @@ export class SyscheckDataService { constructor() { - this.checkConfig$.subscribe(cDef => { + this.checkConfig$.subscribe(() => { this.networkData$.next([]); this.unitData$.next([]); }); diff --git a/src/app/sys-check/unit-check/unit-check.component.html b/src/app/sys-check/unit-check/unit-check.component.html index e1b46fef5ed989145038310ff69c944bea40cf41..46268b6d4f439f31662b1e75e1b72bfe92765265 100644 --- a/src/app/sys-check/unit-check/unit-check.component.html +++ b/src/app/sys-check/unit-check/unit-check.component.html @@ -1,21 +1,14 @@ -<mat-card> - - <mat-card-header> - <mat-card-title> - Test-Aufgabe - <span *ngIf="dataLoading" style="color:red"> - wird gleich geladen, bitte warten.</span> - </mat-card-title> - <mat-card-subtitle>Hier können Sie eine Aufgabe ausprobieren</mat-card-subtitle> - - <tc-navi-buttons></tc-navi-buttons><!-- TODO needed? --> - </mat-card-header> - - <mat-card-content style="height: 610px; overflow: auto"> - <div class="spinner-container-local" *ngIf="dataLoading"> - <mat-spinner></mat-spinner> - </div> - <div style="color:red; position: absolute" *ngIf="errorMessage">{{errorMessage}}</div> - <div #iFrameHost iqbResizeIFrameChild style="height:99%"></div> - </mat-card-content> - -</mat-card> +<mat-card-header> + <mat-card-title> + Test-Aufgabe + <span *ngIf="waitforloading" style="color:red"> - wird geladen, bitte warten.</span> + </mat-card-title> + <mat-card-subtitle>Bitte beachten Sie die Anweisungen in den einzelnen Aufgaben und bearbeiten Sie hierzu die Fragen auf der rechten Seite.</mat-card-subtitle> + + <tc-navi-buttons></tc-navi-buttons><!-- TODO needed? --> +</mat-card-header> + +<mat-card-content style="height: 610px; overflow: auto"> + <div style="color:red; position: absolute" *ngIf="errorMessage">{{errorMessage}}</div> + <div #iFrameHost iqbResizeIFrameChild style="height:99%"></div> +</mat-card-content> diff --git a/src/app/sys-check/unit-check/unit-check.component.ts b/src/app/sys-check/unit-check/unit-check.component.ts index 893a70f6585ceab4d6f229ca92dd06159e443f0d..68db0a6b1d57b1a7699373009072310893b0e115 100644 --- a/src/app/sys-check/unit-check/unit-check.component.ts +++ b/src/app/sys-check/unit-check/unit-check.component.ts @@ -1,10 +1,11 @@ import { MainDataService } from '../../maindata.service'; -import { BackendService, UnitData } from '../backend.service'; +import { BackendService } from '../backend.service'; import { SyscheckDataService } from '../syscheck-data.service'; import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { OnDestroy } from '@angular/core'; import { Subscription, BehaviorSubject, combineLatest} from 'rxjs'; -import { ServerError } from '../../backend.service'; +import { UnitData } from '../sys-check.interfaces'; +import { ServerError } from 'iqb-components'; @Component({ selector: 'iqb-unit-check', @@ -16,12 +17,14 @@ export class UnitCheckComponent implements OnInit, OnDestroy { private iFrameItemplayer: HTMLIFrameElement = null; private postMessageSubscription: Subscription = null; + private taskSubscription: Subscription = null; + private itemplayerPageRequestSubscription = null; private itemplayerSessionId = ''; private postMessageTarget: Window = null; private pendingItemDefinition$ = new BehaviorSubject(null); + waitforloading = true; - public dataLoading = true; public errorMessage = ''; constructor( @@ -33,16 +36,16 @@ export class UnitCheckComponent implements OnInit, OnDestroy { ngOnInit() { - combineLatest( + this.taskSubscription = combineLatest( this.ds.task$, this.ds.checkConfig$ - ).subscribe(([task, checkConfig]) => { - if (task === 'loadunit') { - this.loadUnitAndPlayer(checkConfig.id); - } + ).subscribe(([task, checkConfig]) => { + if (task === 'loadunit') { + this.loadUnitAndPlayer(checkConfig.id); + } }); - this.ds.itemplayerPageRequest$.subscribe((newPage: string) => { + this.itemplayerPageRequestSubscription = this.ds.itemplayerPageRequest$.subscribe((newPage: string) => { if (newPage.length > 0) { this.postMessageTarget.postMessage({ type: 'vo.ToPlayer.NavigateToPage', @@ -95,6 +98,7 @@ export class UnitCheckComponent implements OnInit, OnDestroy { this.ds.itemplayerValidPages$.next([]); this.ds.itemplayerCurrentPage$.next(''); } + this.waitforloading = false; break; case 'vo.FromPlayer.ChangedDataTransfer': @@ -130,7 +134,6 @@ export class UnitCheckComponent implements OnInit, OnDestroy { this.ds.unitData$.next([ {id: '0', type: 'unit/player', label: 'loading error', value: 'Error: ' + unitAndPlayer.labelSystem, warning: true} ]); - this.dataLoading = false; return ''; } @@ -141,7 +144,6 @@ export class UnitCheckComponent implements OnInit, OnDestroy { {id: '0', type: 'unit/player', label: 'loading time', value: unitAndPlayer.duration.toString(), warning: false} ]); console.warn(unitAndPlayer); - this.dataLoading = false; return ''; } @@ -158,9 +160,6 @@ export class UnitCheckComponent implements OnInit, OnDestroy { private clearPlayerElement(): void { - - this.dataLoading = true; - while (this.iFrameHostElement.nativeElement.hasChildNodes()) { this.iFrameHostElement.nativeElement.removeChild(this.iFrameHostElement.nativeElement.lastChild); } @@ -174,10 +173,17 @@ export class UnitCheckComponent implements OnInit, OnDestroy { this.iFrameItemplayer.setAttribute('class', 'unitHost'); this.iFrameItemplayer.setAttribute('height', '100'); this.iFrameHostElement.nativeElement.appendChild(this.iFrameItemplayer); - this.dataLoading = false; } ngOnDestroy() { - this.postMessageSubscription.unsubscribe(); + if (this.taskSubscription !== null) { + this.taskSubscription.unsubscribe(); + } + if (this.itemplayerPageRequestSubscription !== null) { + this.itemplayerPageRequestSubscription.unsubscribe(); + } + if (this.postMessageSubscription !== null) { + this.postMessageSubscription.unsubscribe(); + } } } diff --git a/src/app/test-controller/backend.service.ts b/src/app/test-controller/backend.service.ts index 7d333a03d72867de45aebe04c794605d3079a97b..9c0011c0d920dc5f86c3a0b342562bb319b7fe98 100644 --- a/src/app/test-controller/backend.service.ts +++ b/src/app/test-controller/backend.service.ts @@ -1,9 +1,9 @@ import { Injectable, Inject } from '@angular/core'; import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http'; import { Observable, of } from 'rxjs'; -import { catchError, map, tap, switchMap } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { BookletData, UnitData, TaggedString } from './test-controller.interfaces'; -import { ServerError } from '../backend.service'; +import {ServerError} from "iqb-components"; @Injectable({ @@ -133,7 +133,7 @@ export class BackendService { // 7777777777777777777777777777777777777777777777777777777777777777777777 handle(errorObj: HttpErrorResponse): Observable<ServerError> { - let myreturn: ServerError = null; + let myreturn; if (errorObj.error instanceof ErrorEvent) { myreturn = new ServerError(500, 'Verbindungsproblem', (<ErrorEvent>errorObj.error).message); } else { diff --git a/src/app/test-controller/test-controller.component.ts b/src/app/test-controller/test-controller.component.ts index f5a559199e6b3ae13b1f801f9ff6b335e97b5ac5..d536eac050d8d3d007517c8ee5be9f5b26246da9 100644 --- a/src/app/test-controller/test-controller.component.ts +++ b/src/app/test-controller/test-controller.component.ts @@ -2,16 +2,16 @@ import { ReviewDialogComponent } from './review-dialog/review-dialog.component'; import { MatDialog, MatSnackBar } from '@angular/material'; import { FormGroup } from '@angular/forms'; import { Router } from '@angular/router'; -import { MainDataService } from './../maindata.service'; -import { ServerError } from '../backend.service'; +import { MainDataService } from '../maindata.service'; import { BackendService } from './backend.service'; import { TestControllerService } from './test-controller.service'; import { Component, OnInit, OnDestroy, Inject } from '@angular/core'; -import { UnitDef, Testlet, UnitControllerData, EnvironmentData, MaxTimerData, UnitDefLoadQueue } from './test-controller.classes'; +import { UnitDef, Testlet, EnvironmentData, MaxTimerData } from './test-controller.classes'; import { LastStateKey, LogEntryKey, BookletData, UnitData, MaxTimerDataType, TaggedString } from './test-controller.interfaces'; -import { Subscription, Observable, of, forkJoin, interval, timer, from } from 'rxjs'; -import { switchMap, takeUntil, map, concatMap } from 'rxjs/operators'; +import { Subscription, Observable, of, from } from 'rxjs'; +import { switchMap, concatMap } from 'rxjs/operators'; +import {ServerError} from "iqb-components"; @Component({ templateUrl: './test-controller.component.html', @@ -55,7 +55,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { } } // '''''''''''''''''''''''''''''''''''''''''''''''''''' - private getChildElements(element) { + private static getChildElements(element) { return Array.prototype.slice.call(element.childNodes) .filter(function (e) { return e.nodeType === 1; }); } @@ -63,7 +63,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { // private: recursive reading testlets/units from xml // '''''''''''''''''''''''''''''''''''''''''''''''''''' private addTestletContentFromBookletXml(targetTestlet: Testlet, node: Element) { - const childElements = this.getChildElements(node); + const childElements = TestControllerComponent.getChildElements(node); if (childElements.length > 0) { let codeToEnter = ''; let codePrompt = ''; @@ -77,7 +77,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { } } if (restrictionElement !== null) { - const restrictionElements = this.getChildElements(restrictionElement); + const restrictionElements = TestControllerComponent.getChildElements(restrictionElement); for (let childIndex = 0; childIndex < restrictionElements.length; childIndex++) { if (restrictionElements[childIndex].nodeName === 'CodeToEnter') { const restrictionParameter = restrictionElements[childIndex].getAttribute('parameter'); @@ -123,7 +123,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { } this.allUnitIds.push(myUnitAliasClear); - const newUnit = targetTestlet.addUnit(this.lastUnitSequenceId, myUnitId, + targetTestlet.addUnit(this.lastUnitSequenceId, myUnitId, childElements[childIndex].getAttribute('label'), myUnitAliasClear, childElements[childIndex].getAttribute('labelshort')); this.lastUnitSequenceId += 1; @@ -166,7 +166,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { if (unitsElements.length > 0) { const costumTextsElements = oDOM.documentElement.getElementsByTagName('CustomTexts'); if (costumTextsElements.length > 0) { - const costumTexts = this.getChildElements(costumTextsElements[0]); + const costumTexts = TestControllerComponent.getChildElements(costumTextsElements[0]); const costumTextsForBooklet = {}; for (let childIndex = 0; childIndex < costumTexts.length; childIndex++) { if (costumTexts[childIndex].nodeName === 'Text') { @@ -182,10 +182,10 @@ export class TestControllerComponent implements OnInit, OnDestroy { const bookletConfigElements = oDOM.documentElement.getElementsByTagName('BookletConfig'); if (bookletConfigElements.length > 0) { - const bookletConfigs = this.getChildElements(bookletConfigElements[0]); + const bookletConfigs = TestControllerComponent.getChildElements(bookletConfigElements[0]); for (let childIndex = 0; childIndex < bookletConfigs.length; childIndex++) { const configParameter = bookletConfigs[childIndex].getAttribute('parameter'); - const configValue = bookletConfigs[childIndex].textContent; + // const configValue = bookletConfigs[childIndex].textContent; switch (bookletConfigs[childIndex].nodeName) { // ---------------------- @@ -430,7 +430,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { } switch (navString) { case '#next': - if (this.tcs.rootTestlet !== null) { + if (this.tcs.rootTestlet) { let startWith = this.tcs.currentUnitSequenceId; if (startWith < this.tcs.minUnitSequenceId) { startWith = this.tcs.minUnitSequenceId - 1; @@ -442,17 +442,17 @@ export class TestControllerComponent implements OnInit, OnDestroy { } break; case '#previous': - if (this.tcs.rootTestlet !== null) { + if (this.tcs.rootTestlet) { this.router.navigateByUrl('/t/u/' + (this.tcs.currentUnitSequenceId - 1).toString()); } break; case '#first': - if (this.tcs.rootTestlet !== null) { + if (this.tcs.rootTestlet) { this.router.navigateByUrl('/t/u/' + this.tcs.minUnitSequenceId.toString()); } break; case '#last': - if (this.tcs.rootTestlet !== null) { + if (this.tcs.rootTestlet) { this.router.navigateByUrl('/t/u/' + this.tcs.maxUnitSequenceId.toString()); } break; @@ -461,7 +461,7 @@ export class TestControllerComponent implements OnInit, OnDestroy { break; default: - if (this.tcs.rootTestlet !== null) { + if (this.tcs.rootTestlet) { this.router.navigateByUrl('/t/u/' + navString); } break; diff --git a/src/app/test-controller/test-controller.service.ts b/src/app/test-controller/test-controller.service.ts index 5d26da663c0c61c116b8a955d57070659c27a1c1..f96e0277aeae4d6a74d1ad01cf4744fe84d6c004 100644 --- a/src/app/test-controller/test-controller.service.ts +++ b/src/app/test-controller/test-controller.service.ts @@ -1,12 +1,12 @@ import { debounceTime, takeUntil, map } from 'rxjs/operators'; -import { BehaviorSubject, of, Observable, Subject, Subscription, interval, timer } from 'rxjs'; +import { BehaviorSubject, Subject, Subscription, interval, timer } from 'rxjs'; import { Injectable } from '@angular/core'; import { Testlet, BookletConfig, MaxTimerData } from './test-controller.classes'; import { LastStateKey, LogEntryKey, UnitRestorePointData, UnitResponseData, MaxTimerDataType, UnitNaviButtonData } from './test-controller.interfaces'; import { BackendService } from './backend.service'; -import { ServerError } from '../backend.service'; import { KeyValuePair, KeyValuePairNumber } from '../app.interfaces'; +import {ServerError} from "iqb-components"; @Injectable({ providedIn: 'root' diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts index 1968eda28212700a6c8e75f0b5185d956513d0d2..754a979fe7a54656788c90e87321fb2dddb73d1e 100644 --- a/src/environments/environment.prod.ts +++ b/src/environments/environment.prod.ts @@ -5,5 +5,5 @@ export const environment = { testcenterUrl: '/', appName: 'IQB-Testcenter', appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen', - appVersion: '1.4.0 - 15.1.2020' + appVersion: '1.5.1 - 11.2.2020' }; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 72fe31fad8d9b2074d379db68c1c0a0b1fc0a240..977ca33af6d8aad5c4cfc1702ddd4a845b145659 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -4,9 +4,9 @@ export const environment = { production: false, - // testcenterUrl: 'https://www.iqb-testcenter.de/', - // testcenterUrl: 'https://ocba2.iqb.hu-berlin.de/', - testcenterUrl: 'http://localhost/testcenter-iqb-php/', + testcenterUrl: 'https://www.iqb-testcenter.de/', + // testcenterUrl: 'https://ocba.iqb.hu-berlin.de/', + // testcenterUrl: 'http://localhost/testcenter-iqb-php/', appName: 'IQB-Testcenter', appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen', appVersion: '0 (dev)' diff --git a/src/styles.css b/src/styles.css index 1163ecfde1aeddc5378e773d99f6e8f6ec359d98..f41d547afb8bf470b7f70a364ebec1e9981167d9 100644 --- a/src/styles.css +++ b/src/styles.css @@ -82,7 +82,7 @@ div.logo img { .pagetitle { position: absolute; - left: 100px; + left: 140px; top: 18px; font-size: 1.5em; color: white;