From bb72f9e78b8488b214f211aaadde010ce8454ce2 Mon Sep 17 00:00:00 2001
From: mechtelm <nicht@mehr.fragen>
Date: Mon, 8 Apr 2019 15:25:42 +0200
Subject: [PATCH] workspace admin seems ok

---
 src/app/app-routing.module.ts                 |   2 +-
 src/app/maindata.service.ts                   |  30 ++--
 src/app/start/start.component.ts              |   6 +-
 src/app/workspace/backend.service.ts          |  93 +++----------
 src/app/workspace/files/files.component.css   |   5 +
 src/app/workspace/files/files.component.html  |   8 +-
 src/app/workspace/files/files.component.ts    | 129 +++++++++---------
 .../workspace/monitor/monitor.component.css   |   5 +
 .../workspace/monitor/monitor.component.html  |   9 +-
 .../workspace/monitor/monitor.component.ts    |  72 ++++++----
 .../workspace/results/results.component.css   |  13 +-
 .../workspace/results/results.component.html  |   9 +-
 .../workspace/results/results.component.ts    |  43 ++++--
 src/app/workspace/workspace-routing.module.ts |   4 +-
 src/app/workspace/workspace.component.html    |  12 +-
 src/app/workspace/workspace.component.ts      |  22 +--
 src/app/workspace/workspace.interfaces.ts     |   4 +
 src/app/workspace/workspacedata.service.ts    |  47 ++++++-
 18 files changed, 283 insertions(+), 230 deletions(-)

diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index e4c0b4dd..97b8c11a 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -15,7 +15,7 @@ const routes: Routes = [
 ];
 
 @NgModule({
-  imports: [RouterModule.forRoot(routes)],
+  imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})],
   exports: [RouterModule]
 })
 export class AppRoutingModule { }
diff --git a/src/app/maindata.service.ts b/src/app/maindata.service.ts
index 70561232..acce73d4 100644
--- a/src/app/maindata.service.ts
+++ b/src/app/maindata.service.ts
@@ -46,6 +46,8 @@ export class MainDataService {
           myLoginData.name = logindata.name;
           myLoginData.workspaces = logindata.workspaces;
           myLoginData.is_superadmin = logindata.is_superadmin;
+          console.log('fü33 ' + myLoginData.name);
+
       }
     }
     this.loginData$.next(myLoginData);
@@ -60,12 +62,14 @@ export class MainDataService {
   // $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
   getWorkspaceName(ws: number): string {
     let myreturn = '';
-    const myLoginData = this.loginData$.getValue();
-    if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) {
-      for (let i = 0; i < myLoginData.workspaces.length; i++) {
-        if (myLoginData.workspaces[i].id == ws) {
-          myreturn = myLoginData.workspaces[i].name;
-          break;
+    if (ws > 0) {
+      const myLoginData = this.loginData$.getValue();
+      if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) {
+        for (let i = 0; i < myLoginData.workspaces.length; i++) {
+          if (myLoginData.workspaces[i].id == ws) {
+            myreturn = myLoginData.workspaces[i].name;
+            break;
+          }
         }
       }
     }
@@ -75,12 +79,14 @@ export class MainDataService {
   // $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
   getWorkspaceRole(ws: number): string {
     let myreturn = '';
-    const myLoginData = this.loginData$.getValue();
-    if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) {
-      for (let i = 0; i < myLoginData.workspaces.length; i++) {
-        if (myLoginData.workspaces[i].id == ws) {
-          myreturn = myLoginData.workspaces[i].role;
-          break;
+    if (ws > 0) {
+      const myLoginData = this.loginData$.getValue();
+      if ((myLoginData !== null) && (myLoginData.workspaces.length > 0)) {
+        for (let i = 0; i < myLoginData.workspaces.length; i++) {
+          if (myLoginData.workspaces[i].id == ws) {
+            myreturn = myLoginData.workspaces[i].role;
+            break;
+          }
         }
       }
     }
diff --git a/src/app/start/start.component.ts b/src/app/start/start.component.ts
index 08b7fb9c..2192559d 100644
--- a/src/app/start/start.component.ts
+++ b/src/app/start/start.component.ts
@@ -49,7 +49,11 @@ export class StartComponent implements OnInit, OnDestroy {
   }
 
   buttonGotoWorkspace(ws: WorkspaceData) {
-    this.router.navigateByUrl('/ws/' + ws.id.toString());
+    if (ws.role === 'MO') {
+      this.router.navigateByUrl('/ws/' + ws.id.toString() + '/monitor');
+    } else {
+      this.router.navigateByUrl('/ws/' + ws.id.toString() + '/files');
+    }
   }
 
   // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
diff --git a/src/app/workspace/backend.service.ts b/src/app/workspace/backend.service.ts
index e56ca36f..9d62ebf5 100644
--- a/src/app/workspace/backend.service.ts
+++ b/src/app/workspace/backend.service.ts
@@ -14,7 +14,7 @@ export class BackendService {
     @Inject('SERVER_URL') private serverUrl: string,
     private http: HttpClient) {
       this.serverUrlSlim = this.serverUrl + 'php/ws.php/'
-      this.serverUrl = this.serverUrl + 'php_admin/';
+      this.serverUrl = this.serverUrl + 'php/';
   }
 
 
@@ -28,15 +28,9 @@ export class BackendService {
   }
 
   // *******************************************************************
-  // Fehlerbehandlung beim Aufrufer
   deleteFiles(filesToDelete: Array<string>): Observable<string | ServerError> {
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
     return this.http
-      .post<string>(this.serverUrl + 'deleteFiles.php', {f: filesToDelete}, httpOptions)
+      .post<string>(this.serverUrlSlim + 'delete', {f: filesToDelete})
         .pipe(
           catchError(ErrorHandler.handle)
         );
@@ -44,13 +38,8 @@ export class BackendService {
 
   // *******************************************************************
   checkWorkspace(): Observable<CheckWorkspaceResponseData | ServerError> {
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
     return this.http
-      .post<CheckWorkspaceResponseData>(this.serverUrl + 'checkWorkspace.php', {}, httpOptions)
+      .post<CheckWorkspaceResponseData>(this.serverUrl + 'checkWorkspace.php', {})
         .pipe(
           catchError(ErrorHandler.handle)
         );
@@ -58,13 +47,8 @@ export class BackendService {
 
   /*******************************/
   getBookletsStarted(groups: string[]): Observable<BookletsStarted[] | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
     return this.http
-      .post<BookletsStarted[]>(this.serverUrl + 'getBookletsStarted.php', {g: groups}, httpOptions)
+      .post<BookletsStarted[]>(this.serverUrl + 'getBookletsStarted.php', {g: groups})
         .pipe(
           catchError(ErrorHandler.handle)
         );
@@ -72,13 +56,8 @@ export class BackendService {
 
   /*******************************/
   lockBooklets(groups: string[]): Observable<boolean | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
     return this.http
-      .post<boolean>(this.serverUrl + 'lockBooklets.php', {g: groups}, httpOptions)
+      .post<boolean>(this.serverUrlSlim + 'lock', {g: groups})
         .pipe(
           catchError(ErrorHandler.handle)
         );
@@ -86,14 +65,9 @@ export class BackendService {
 
   /*******************************/
   unlockBooklets(groups: string[]): Observable<boolean | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
     return this.http
-      .post<boolean>(this.serverUrl + 'unlockBooklets.php', {g: groups}, httpOptions)
-        .pipe(
+    .post<boolean>(this.serverUrlSlim + 'unlock', {g: groups})
+      .pipe(
           catchError(ErrorHandler.handle)
         );
 }
@@ -113,70 +87,45 @@ export class BackendService {
 }
 
   /*******************************/
-  getResultData(): Observable<ResultData[] | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
+  getResultData(): Observable<ResultData[]>{
     return this.http
-      .post<ResultData[]>(this.serverUrl + 'getResultData.php', {}, httpOptions)
+      .post<ResultData[]>(this.serverUrl + 'getResultData.php', {})
         .pipe(
-          catchError(ErrorHandler.handle)
+          catchError(err => [])
         );
   }
 
   /*******************************/
-  getResponses(groups: string[]): Observable<UnitResponse[] | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
+  getResponses(groups: string[]): Observable<UnitResponse[]>{
     return this.http
-      .post<UnitResponse[]>(this.serverUrl + 'getResponses.php', {g: groups}, httpOptions)
+      .post<UnitResponse[]>(this.serverUrl + 'getResponses.php', {g: groups})
         .pipe(
-          catchError(ErrorHandler.handle)
+          catchError(err => [])
         );
-}
+  }
 
   /*******************************/
-  getLogs(groups: string[]): Observable<LogData[] | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
+  getLogs(groups: string[]): Observable<LogData[]>{
     return this.http
-      .post<LogData[]>(this.serverUrl + 'getLogs.php', {g: groups}, httpOptions)
+      .post<LogData[]>(this.serverUrl + 'getLogs.php', {g: groups})
         .pipe(
-          catchError(ErrorHandler.handle)
+          catchError(err => [])
         );
   }
 
   /*******************************/
-  getReviews(groups: string[]): Observable<ReviewData[] | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
+  getReviews(groups: string[]): Observable<ReviewData[]>{
     return this.http
-      .post<ReviewData[]>(this.serverUrl + 'getReviews.php', {g: groups}, httpOptions)
+      .post<ReviewData[]>(this.serverUrl + 'getReviews.php', {g: groups})
         .pipe(
-          catchError(ErrorHandler.handle)
+          catchError(err => [])
         );
   }
 
   /*******************************/
   deleteData(groups: string[]): Observable<boolean | ServerError>{
-    const httpOptions = {
-      headers: new HttpHeaders({
-        'Content-Type':  'application/json'
-      })
-    };
     return this.http
-      .post<boolean>(this.serverUrl + 'deleteData.php', {g: groups}, httpOptions)
+      .post<boolean>(this.serverUrl + 'deleteData.php', {g: groups})
         .pipe(
           catchError(ErrorHandler.handle)
         );
diff --git a/src/app/workspace/files/files.component.css b/src/app/workspace/files/files.component.css
index ee1e74d5..58d6fe85 100644
--- a/src/app/workspace/files/files.component.css
+++ b/src/app/workspace/files/files.component.css
@@ -43,3 +43,8 @@
 .checkinfo {
   color: darkgreen;
 }
+
+.mat-raised-button {
+  min-width: 100px;
+  margin: 2px;
+}
\ No newline at end of file
diff --git a/src/app/workspace/files/files.component.html b/src/app/workspace/files/files.component.html
index a4989ec2..d611835a 100644
--- a/src/app/workspace/files/files.component.html
+++ b/src/app/workspace/files/files.component.html
@@ -43,10 +43,10 @@
 
   <!-- ============================================= -->
   <div class="uploads">
-    <button mat-raised-button (click)="deleteFiles()" matTooltip="Markierte Dateien löschen" matTooltipPosition="above">
+    <button mat-raised-button (click)="deleteFiles()" matTooltip="Markierte Dateien löschen" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'">
       <mat-icon>delete</mat-icon>
     </button>
-    <button mat-raised-button (click)="hiddenfileinput.click()" matTooltip="Dateien hochladen/aktualisieren" matTooltipPosition="above">
+    <button mat-raised-button (click)="hiddenfileinput.click()" matTooltip="Dateien hochladen/aktualisieren" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'">
       <mat-icon>cloud_upload</mat-icon>
     </button>
     <button mat-raised-button (click)="checkWorkspace()" matTooltip="Arbeitsbereich prüfen" matTooltipPosition="above">
@@ -59,9 +59,9 @@
       [httpUrl]="uploadUrl"
       [fileAlias]="fileNameAlias"
       [tokenName]="'at'"
-      [token]="token"
+      [token]="'kisduUjjw.;kiskw..9200'"
       [folderName]="'ws'"
-      [folder]="workspace"
+      [folder]="'workspace'"
       (uploadCompleteEvent)="updateFileList()">
     </iqb-files-upload-queue>
 
diff --git a/src/app/workspace/files/files.component.ts b/src/app/workspace/files/files.component.ts
index e9350259..f3a49d04 100644
--- a/src/app/workspace/files/files.component.ts
+++ b/src/app/workspace/files/files.component.ts
@@ -24,10 +24,12 @@ import { IqbFilesUploadQueueComponent, IqbFilesUploadInputForDirective } from '.
 export class FilesComponent implements OnInit, OnDestroy {
   public serverfiles: MatTableDataSource<GetFileResponseData>;
   public displayedColumns = ['checked', 'filename', 'typelabel', 'filesize', 'filedatetime'];
+  public dataLoading = false;
+  private workspaceIdSubscription: Subscription = null;
+
+  // for fileupload
   public uploadUrl = '';
   public fileNameAlias = 'fileforvo';
-  public dataLoading = false;
-  private logindataSubscription: Subscription = null;
 
   // for workspace-check
   public checkErrors = [];
@@ -45,14 +47,12 @@ export class FilesComponent implements OnInit, OnDestroy {
     public messsageDialog: MatDialog,
     public snackBar: MatSnackBar
   ) {
-    this.uploadUrl = this.serverUrl + 'php_admin/uploadFile.php';
+    this.uploadUrl = this.serverUrl + 'php/uploadFile.php';
   }
 
   ngOnInit() {
-    this.logindataSubscription = this.mds.loginData$.subscribe(ld => {
-        const ws = this.wds.ws;
-        let at = ld ? ld.admintoken : '';
-        this.updateFileList((ws <= 0) || (at.length === 0));
+    this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(ws => {
+      this.updateFileList((this.wds.ws <= 0) || (this.mds.adminToken.length === 0));
     });
   }
 
@@ -65,65 +65,66 @@ export class FilesComponent implements OnInit, OnDestroy {
 
   // ***********************************************************************************
   deleteFiles() {
-    this.checkErrors = [];
-    this.checkWarnings = [];
-    this.checkInfos = [];
-
-    const filesToDelete = [];
-    this.serverfiles.data.forEach(element => {
-      if (element.isChecked) {
-        filesToDelete.push(element.type + '::' + element.filename);
-      }
-    });
-
-    if (filesToDelete.length > 0) {
-      let prompt = 'Sie haben ';
-      if (filesToDelete.length > 1) {
-        prompt = prompt + filesToDelete.length + ' Dateien ausgewählt. Sollen';
-      } else {
-        prompt = prompt + ' eine Datei ausgewählt. Soll';
-      }
-      const dialogRef = this.confirmDialog.open(ConfirmDialogComponent, {
-        width: '400px',
-        data: <ConfirmDialogData>{
-          title: 'Löschen von Dateien',
-          content: prompt + ' diese gelöscht werden?',
-          confirmbuttonlabel: 'Löschen'
+    if (this.wds.wsRole === 'RW') {
+      this.checkErrors = [];
+      this.checkWarnings = [];
+      this.checkInfos = [];
+
+      const filesToDelete = [];
+      this.serverfiles.data.forEach(element => {
+        if (element.isChecked) {
+          filesToDelete.push(element.type + '::' + element.filename);
         }
       });
 
-      dialogRef.afterClosed().subscribe(result => {
-        if (result !== false) {
-          // =========================================================
-          this.dataLoading = true;
-          this.bs.deleteFiles(filesToDelete).subscribe(deletefilesresponse => {
-            if (deletefilesresponse instanceof ServerError) {
-              this.wds.setNewErrorMsg(deletefilesresponse as ServerError);
-            } else {
-              const deletefilesresponseOk = deletefilesresponse as string;
-              if ((deletefilesresponseOk.length > 5) && (deletefilesresponseOk.substr(0, 2) === 'e:')) {
-                this.snackBar.open(deletefilesresponseOk.substr(2), 'Fehler', {duration: 1000});
+      if (filesToDelete.length > 0) {
+        let prompt = 'Sie haben ';
+        if (filesToDelete.length > 1) {
+          prompt = prompt + filesToDelete.length + ' Dateien ausgewählt. Sollen';
+        } else {
+          prompt = prompt + ' eine Datei ausgewählt. Soll';
+        }
+        const dialogRef = this.confirmDialog.open(ConfirmDialogComponent, {
+          width: '400px',
+          data: <ConfirmDialogData>{
+            title: 'Löschen von Dateien',
+            content: prompt + ' diese gelöscht werden?',
+            confirmbuttonlabel: 'Löschen'
+          }
+        });
+
+        dialogRef.afterClosed().subscribe(result => {
+          if (result !== false) {
+            // =========================================================
+            this.dataLoading = true;
+            this.bs.deleteFiles(filesToDelete).subscribe(deletefilesresponse => {
+              if (deletefilesresponse instanceof ServerError) {
+                this.wds.setNewErrorMsg(deletefilesresponse as ServerError);
               } else {
-                this.snackBar.open(deletefilesresponseOk, '', {duration: 1000});
-                this.updateFileList();
+                const deletefilesresponseOk = deletefilesresponse as string;
+                if ((deletefilesresponseOk.length > 5) && (deletefilesresponseOk.substr(0, 2) === 'e:')) {
+                  this.snackBar.open(deletefilesresponseOk.substr(2), 'Fehler', {duration: 1000});
+                } else {
+                  this.snackBar.open(deletefilesresponseOk, '', {duration: 1000});
+                  this.updateFileList();
+                }
+                this.wds.setNewErrorMsg();
               }
-              this.wds.setNewErrorMsg();
-            }
-          });
-          // =========================================================
-        }
-      });
-    } else {
-      this.messsageDialog.open(MessageDialogComponent, {
-        width: '400px',
-        data: <MessageDialogData>{
-          title: 'Löschen von Dateien',
-          content: 'Bitte markieren Sie erst Dateien!',
-          type: MessageType.error
-        }
-      });
+            });
+            // =========================================================
+          }
+        });
+      } else {
+        this.messsageDialog.open(MessageDialogComponent, {
+          width: '400px',
+          data: <MessageDialogData>{
+            title: 'Löschen von Dateien',
+            content: 'Bitte markieren Sie erst Dateien!',
+            type: MessageType.error
+          }
+        });
+      }
     }
-
   }
 
   // ***********************************************************************************
@@ -132,7 +133,7 @@ export class FilesComponent implements OnInit, OnDestroy {
     this.checkWarnings = [];
     this.checkInfos = [];
 
-    if (empty) {
+    if (empty || this.wds.wsRole === 'MO') {
       this.serverfiles = new MatTableDataSource([]);
     } else {
       this.dataLoading = true;
@@ -167,8 +168,6 @@ export class FilesComponent implements OnInit, OnDestroy {
     this.dataLoading = true;
     this.bs.checkWorkspace().subscribe(
       (checkResponse: CheckWorkspaceResponseData) => {
-        // this.serverfiles = new MatTableDataSource(filedataresponse);
-        // this.serverfiles.sort = this.sort;
         this.checkErrors = checkResponse.errors;
         this.checkWarnings = checkResponse.warnings;
         this.checkInfos = checkResponse.infos;
@@ -184,8 +183,8 @@ export class FilesComponent implements OnInit, OnDestroy {
 
   // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
   ngOnDestroy() {
-    if (this.logindataSubscription !== null) {
-      this.logindataSubscription.unsubscribe();
+    if (this.workspaceIdSubscription !== null) {
+      this.workspaceIdSubscription.unsubscribe();
     }
   }
 }
diff --git a/src/app/workspace/monitor/monitor.component.css b/src/app/workspace/monitor/monitor.component.css
index 0e471019..1c9da90e 100644
--- a/src/app/workspace/monitor/monitor.component.css
+++ b/src/app/workspace/monitor/monitor.component.css
@@ -22,3 +22,8 @@
 .cellstyle1 {
   padding: 5px;
 }
+
+.mat-raised-button {
+  margin: 5px;
+  min-width: 100px;
+}
\ No newline at end of file
diff --git a/src/app/workspace/monitor/monitor.component.html b/src/app/workspace/monitor/monitor.component.html
index d823a3a7..6fbe2f3a 100644
--- a/src/app/workspace/monitor/monitor.component.html
+++ b/src/app/workspace/monitor/monitor.component.html
@@ -7,12 +7,12 @@
         matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
       <mat-icon>cloud_download</mat-icon>
     </button>
-    <button mat-raised-button (click)="lock()" [disabled]="!tableselectionCheckbox.hasValue()"
+    <button mat-raised-button (click)="lock()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')"
         matTooltip="Sperre gestartete Testhefte für markierte Gruppen" matTooltipPosition="above">
       <mat-icon>lock</mat-icon>
     </button>
 
-    <button mat-raised-button (click)="unlock()" [disabled]="!tableselectionCheckbox.hasValue()"
+    <button mat-raised-button (click)="unlock()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')"
         matTooltip="Gesperrte Testhefte für markierte Gruppen freigeben" matTooltipPosition="above">
       <mat-icon>lock_open</mat-icon>
     </button>
@@ -64,6 +64,11 @@
       <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.bookletsLocked }} </mat-cell>
     </ng-container>
 
+    <ng-container matColumnDef="laststart">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Letzter Start</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center"> {{ element.laststartStr }} </mat-cell>
+    </ng-container>
+
     <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
     <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
   </mat-table>
diff --git a/src/app/workspace/monitor/monitor.component.ts b/src/app/workspace/monitor/monitor.component.ts
index 73cd8731..ffbbc201 100644
--- a/src/app/workspace/monitor/monitor.component.ts
+++ b/src/app/workspace/monitor/monitor.component.ts
@@ -1,23 +1,26 @@
 import { BookletsStarted } from './../workspace.interfaces';
 import { WorkspaceDataService } from './../workspacedata.service';
 import { BackendService } from './../backend.service';
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
 import { MatSnackBar, MatSort, MatTableDataSource } from '@angular/material';
 import { SelectionModel } from '@angular/cdk/collections';
 import { saveAs } from 'file-saver';
 import { MonitorData } from '../workspace.interfaces';
+import { Subscription } from 'rxjs';
+import { ServerError } from 'src/app/backend.service';
 
 
 @Component({
   templateUrl: './monitor.component.html',
   styleUrls: ['./monitor.component.css']
 })
-export class MonitorComponent implements OnInit {
+export class MonitorComponent implements OnInit, OnDestroy {
 
   displayedColumns: string[] = ['selectCheckbox', 'groupname', 'loginsPrepared',
-          'personsPrepared', 'bookletsPrepared', 'bookletsStarted', 'bookletsLocked'];
+          'personsPrepared', 'bookletsPrepared', 'bookletsStarted', 'bookletsLocked', 'laststart'];
   private monitorDataSource = new MatTableDataSource<MonitorData>([]);
   private tableselectionCheckbox = new SelectionModel<MonitorData>(true, []);
+  private workspaceIdSubscription: Subscription = null;
   private dataLoading = false;
   @ViewChild(MatSort) sort: MatSort;
 
@@ -28,12 +31,14 @@ export class MonitorComponent implements OnInit {
   ) { }
 
   ngOnInit() {
-    this.updateTable();
+    this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(ws => {
+      this.updateTable();
+    });
   }
 
   updateTable() {
     this.dataLoading = true;
-    this.tableselectionCheckbox.clear;
+    this.tableselectionCheckbox.clear();
     this.bs.getMonitorData().subscribe(
       (monitorData: MonitorData[]) => {
         this.dataLoading = false;
@@ -71,10 +76,10 @@ export class MonitorComponent implements OnInit {
             const lineDelimiter = '\n';
 
             let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter +
-                'bookletname' + columnDelimiter + 'locked' + lineDelimiter;
+                'bookletname' + columnDelimiter + 'locked' + columnDelimiter + 'laststart' + lineDelimiter;
             bookletList.forEach((b: BookletsStarted) => {
                myCsvData += '"' + b.groupname + '"' + columnDelimiter + '"' + b.loginname + '"' + columnDelimiter + '"' + b.code + '"' + columnDelimiter +
-                '"' + b.bookletname + '"' + columnDelimiter + '"' + (b.locked ? 'X' : '-') + '"' + lineDelimiter;
+                '"' + b.bookletname + '"' + columnDelimiter + '"' + (b.locked ? 'X' : '-') + '"' + columnDelimiter + '"' + b.laststart + '"' + lineDelimiter;
             });
             var blob = new Blob([myCsvData], {type: "text/csv;charset=utf-8"});
             saveAs(blob, "iqb-testcenter-bookletsStarted.csv");
@@ -97,16 +102,21 @@ export class MonitorComponent implements OnInit {
         selectedGroups.push(element.groupname);
       });
       this.bs.lockBooklets(selectedGroups).subscribe(success => {
-              const ok = success as boolean;
-              if (ok) {
-                this.snackBar.open('Testhefte wurden gesperrt.', 'Sperrung', {duration: 1000});
-              } else {
-                this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Fehler', {duration: 3000});
-              }
-            });
-            this.dataLoading = false;
-            this.updateTable();
+        if (success instanceof ServerError) {
+          this.wds.setNewErrorMsg(success as ServerError);
+          this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Systemfehler', {duration: 3000});
+        } else {
+          const ok = success as boolean;
+          if (ok) {
+            this.snackBar.open('Testhefte wurden gesperrt.', 'Sperrung', {duration: 1000});
+          } else {
+            this.snackBar.open('Testhefte konnten nicht gesperrt werden.', 'Fehler', {duration: 3000});
           }
+        }
+        this.dataLoading = false;
+        this.updateTable();
+      });
+    }
   }
 
   unlock() {
@@ -117,15 +127,27 @@ export class MonitorComponent implements OnInit {
         selectedGroups.push(element.groupname);
       });
       this.bs.unlockBooklets(selectedGroups).subscribe(success => {
-              const ok = success as boolean;
-              if (ok) {
-                this.snackBar.open('Testhefte wurden freigegeben.', 'Sperrung', {duration: 1000});
-              } else {
-                this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Fehler', {duration: 3000});
-              }
-            });
-            this.dataLoading = false;
-            this.updateTable();
+        if (success instanceof ServerError) {
+          this.wds.setNewErrorMsg(success as ServerError);
+          this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Systemfehler', {duration: 3000});
+        } else {
+          const ok = success as boolean;
+          if (ok) {
+            this.snackBar.open('Testhefte wurden freigegeben.', 'Sperrung', {duration: 1000});
+          } else {
+            this.snackBar.open('Testhefte konnten nicht freigegeben werden.', 'Fehler', {duration: 3000});
           }
+        }
+        this.dataLoading = false;
+        this.updateTable();
+      });
+    }
+  }
+
+  // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
+  ngOnDestroy() {
+    if (this.workspaceIdSubscription !== null) {
+      this.workspaceIdSubscription.unsubscribe();
+    }
   }
 }
diff --git a/src/app/workspace/results/results.component.css b/src/app/workspace/results/results.component.css
index 3c577b39..e43a89f4 100644
--- a/src/app/workspace/results/results.component.css
+++ b/src/app/workspace/results/results.component.css
@@ -1,12 +1,17 @@
-.columnhost {
+/* .columnhost {
   width: 100%;
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
   align-items: flex-start;
   justify-content: left;
-}
+} */
 
-.mat-icon {
+/* .mat-icon {
   margin-right: 5px;
-}
+} */
+
+.mat-raised-button {
+  min-width: 100px;
+  margin: 2px;
+}
\ No newline at end of file
diff --git a/src/app/workspace/results/results.component.html b/src/app/workspace/results/results.component.html
index c28f9545..5ac0ebf4 100644
--- a/src/app/workspace/results/results.component.html
+++ b/src/app/workspace/results/results.component.html
@@ -2,7 +2,7 @@
   <div class="spinner-container" *ngIf="dataLoading">
     <mat-spinner></mat-spinner>
   </div>
-  <div fxLayout="row" fxLayoutGap="10px">
+  <div fxLayout="row">
     <button mat-raised-button (click)="downloadResponsesCSV()" [disabled]="!tableselectionCheckbox.hasValue()"
         matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
       <mat-icon>cloud_download</mat-icon>Antworten
@@ -15,7 +15,7 @@
         matTooltip="Download markierte Gruppen als CSV für Excel" matTooltipPosition="above">
       <mat-icon>cloud_download</mat-icon>Kommentare
     </button>
-    <button mat-raised-button (click)="deleteData()" [disabled]="!tableselectionCheckbox.hasValue()"
+    <button mat-raised-button (click)="deleteData()" [disabled]="!tableselectionCheckbox.hasValue() || (wds.wsRole !== 'RW')"
         matTooltip="Löschen Ergebnisdaten aus der Datenbank für markierte Gruppen" matTooltipPosition="above">
       <mat-icon>delete</mat-icon>
     </button>
@@ -62,6 +62,11 @@
       <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.num_units_mean | number:'1.1-1'}} </mat-cell>
     </ng-container>
 
+    <ng-container matColumnDef="lastchange">
+      <mat-header-cell *matHeaderCellDef mat-sort-header fxLayoutAlign="center center">Letzte Änderung</mat-header-cell>
+      <mat-cell *matCellDef="let element" fxLayoutAlign="center center">{{element.lastchange | date:'dd.MM.yyyy HH:mm'}} </mat-cell>
+    </ng-container>
+
     <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
     <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
   </mat-table>
diff --git a/src/app/workspace/results/results.component.ts b/src/app/workspace/results/results.component.ts
index e813e918..5b85a8e8 100644
--- a/src/app/workspace/results/results.component.ts
+++ b/src/app/workspace/results/results.component.ts
@@ -1,24 +1,26 @@
 import { LogData } from './../workspace.interfaces';
 import { WorkspaceDataService } from './../workspacedata.service';
 import { ConfirmDialogComponent, ConfirmDialogData } from './../../iqb-common/confirm-dialog/confirm-dialog.component';
-import { Component, OnInit, ViewChild } from '@angular/core';
+import { Component, OnInit, ViewChild, OnDestroy } from '@angular/core';
 import { BackendService } from './../backend.service';
 import { MatSnackBar, MatSort, MatTableDataSource, MatDialog } from '@angular/material';
 import { SelectionModel } from '@angular/cdk/collections';
 import { saveAs } from 'file-saver';
 import { ResultData, UnitResponse, ReviewData } from '../workspace.interfaces';
+import { Subscription } from 'rxjs';
 
 
 @Component({
   templateUrl: './results.component.html',
   styleUrls: ['./results.component.css']
 })
-export class ResultsComponent implements OnInit {
-  displayedColumns: string[] = ['selectCheckbox', 'groupname', 'bookletsStarted', 'num_units_min', 'num_units_max', 'num_units_mean'];
+export class ResultsComponent implements OnInit, OnDestroy {
+  displayedColumns: string[] = ['selectCheckbox', 'groupname', 'bookletsStarted', 'num_units_min', 'num_units_max', 'num_units_mean', 'lastchange'];
   private resultDataSource = new MatTableDataSource<ResultData>([]);
   // prepared for selection if needed sometime
   private tableselectionCheckbox = new SelectionModel<ResultData>(true, []);
   private dataLoading = false;
+  private workspaceIdSubscription: Subscription = null;
 
   @ViewChild(MatSort) sort: MatSort;
 
@@ -30,19 +32,25 @@ export class ResultsComponent implements OnInit {
   ) { }
 
   ngOnInit() {
-    this.updateTable();
+    this.workspaceIdSubscription = this.wds.workspaceId$.subscribe(ws => {
+      this.updateTable();
+    });
   }
 
   updateTable() {
-    this.dataLoading = true;
     this.tableselectionCheckbox.clear();
-    this.bs.getResultData().subscribe(
-      (resultData: ResultData[]) => {
-        this.dataLoading = false;
-        this.resultDataSource = new MatTableDataSource<ResultData>(resultData);
-        this.resultDataSource.sort = this.sort;
-      }
-    )
+    if (this.wds.wsRole === 'MO') {
+      this.resultDataSource = new MatTableDataSource<ResultData>([]);
+    } else {
+      this.dataLoading = true;
+      this.bs.getResultData().subscribe(
+        (resultData: ResultData[]) => {
+          this.dataLoading = false;
+          this.resultDataSource = new MatTableDataSource<ResultData>(resultData);
+          this.resultDataSource.sort = this.sort;
+        }
+      )
+    }
   }
 
   isAllSelected() {
@@ -72,8 +80,8 @@ export class ResultsComponent implements OnInit {
           const lineDelimiter = '\n';
           let myCsvData = 'groupname' + columnDelimiter + 'loginname' + columnDelimiter + 'code' + columnDelimiter +
               'bookletname' + columnDelimiter + 'unitname' + columnDelimiter + 'responses' + columnDelimiter +
-              'restorePoint' + columnDelimiter + 'responseType' + lineDelimiter + 'response-ts' + lineDelimiter +
-              'restorePoint-ts' + lineDelimiter + 'laststate';
+              'restorePoint' + columnDelimiter + 'responseType' + columnDelimiter + 'response-ts' + columnDelimiter +
+              'restorePoint-ts' + columnDelimiter + 'laststate' + lineDelimiter;
           responseData.forEach((resp: UnitResponse) => {
             myCsvData += '"' + resp.groupname + '"' + columnDelimiter + '"' + resp.loginname + '"' + columnDelimiter + '"' + resp.code + '"' + columnDelimiter +
                 '"' + resp.bookletname + '"' + columnDelimiter + '"' + resp.unitname + '"' + columnDelimiter;
@@ -239,4 +247,11 @@ export class ResultsComponent implements OnInit {
         });
     }
   }
+
+  // % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % %
+  ngOnDestroy() {
+    if (this.workspaceIdSubscription !== null) {
+      this.workspaceIdSubscription.unsubscribe();
+    }
+  }
 }
diff --git a/src/app/workspace/workspace-routing.module.ts b/src/app/workspace/workspace-routing.module.ts
index 0761950b..d4068ec0 100644
--- a/src/app/workspace/workspace-routing.module.ts
+++ b/src/app/workspace/workspace-routing.module.ts
@@ -11,12 +11,12 @@ const routes: Routes = [
     path: 'ws/:ws',
     component: WorkspaceComponent,
     children: [
-      {path: '', redirectTo: 'files', pathMatch: 'full'},
+      {path: '', redirectTo: 'monitor', pathMatch: 'full'},
       {path: 'files', component: FilesComponent},
       {path: 'syscheck', component: SyscheckComponent},
       {path: 'monitor', component: MonitorComponent},
       {path: 'results', component: ResultsComponent},
-      {path: '**', component: FilesComponent}
+      {path: '**', component: MonitorComponent}
     ]
   }];
 
diff --git a/src/app/workspace/workspace.component.html b/src/app/workspace/workspace.component.html
index 45c74fcb..6f5cf00c 100644
--- a/src/app/workspace/workspace.component.html
+++ b/src/app/workspace/workspace.component.html
@@ -1,10 +1,12 @@
-<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="space-between center">
+<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="start center">
   <a [routerLink]="['/']">
     <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/>
   </a>
-  <div class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</div>
-  <div>IQB-Testcenter Verwaltung</div>
-  <div>{{ pageTitle }}</div>
+  <div fxLayout="row wrap" fxLayoutAlign="space-between center" fxFlex>
+    <div class="error-msg">{{ (mds.globalErrorMsg$ | async)?.labelNice }}</div>
+    <div>IQB-Testcenter Verwaltung</div>
+    <div>{{ wds.wsName }} ({{ wds.wsRole }})</div>
+  </div>
 </div>
 <div class="page-body">
   <div class="adminbackground">
@@ -12,7 +14,7 @@
 
     <nav mat-tab-nav-bar>
       <a mat-tab-link
-          *ngFor="let link of navLinks"
+          *ngFor="let link of wds.navLinks"
           [routerLink]="link.path"
           routerLinkActive #rla="routerLinkActive"
           [active]="rla.isActive">
diff --git a/src/app/workspace/workspace.component.ts b/src/app/workspace/workspace.component.ts
index 2700ee25..c1380957 100644
--- a/src/app/workspace/workspace.component.ts
+++ b/src/app/workspace/workspace.component.ts
@@ -11,14 +11,6 @@ import { MatTabsModule, MatSelectModule, MatFormFieldModule } from '@angular/mat
   styleUrls: ['./workspace.component.css']
 })
 export class WorkspaceComponent implements OnInit, OnDestroy {
-  public navLinks = [
-    {path: 'files', label: 'Dateien'},
-    {path: 'syscheck', label: 'System-Check Berichte'},
-    {path: 'monitor', label: 'Monitor'},
-    {path: 'results', label: 'Ergebnisse'}
-  ];
-
-  public pageTitle = '';
   private routingSubscription: Subscription = null;
   private logindataSubscription: Subscription = null;
 
@@ -33,21 +25,11 @@ export class WorkspaceComponent implements OnInit, OnDestroy {
   ngOnInit() {
     this.routingSubscription = this.route.params.subscribe(params => {
       const ws = Number(params['ws']);
-      this.wds.setWorkspaceId(ws);
-      if ((this.mds.adminToken.length > 0) && (ws > 0)) {
-        this.pageTitle = this.mds.getWorkspaceName(ws) + ' (' + this.mds.getWorkspaceRole(ws) + ')';
-      } else {
-        this.pageTitle = '';
-      }
+      this.wds.setWorkspace(ws, this.mds.getWorkspaceRole(ws), this.mds.getWorkspaceName(ws));
     });
 
     this.logindataSubscription = this.mds.loginData$.subscribe(ld => {
-      const ws = this.wds.ws;
-      if (ws > 0) {
-        this.pageTitle = this.mds.getWorkspaceName(ws) + ' (' + this.mds.getWorkspaceRole(ws) + ')';
-      } else {
-        this.pageTitle = '';
-      }
+      this.wds.setWorkspace(this.wds.ws, this.mds.getWorkspaceRole(this.wds.ws), this.mds.getWorkspaceName(this.wds.ws));
     });
   }
 
diff --git a/src/app/workspace/workspace.interfaces.ts b/src/app/workspace/workspace.interfaces.ts
index 2053eb37..b53f17d1 100644
--- a/src/app/workspace/workspace.interfaces.ts
+++ b/src/app/workspace/workspace.interfaces.ts
@@ -29,6 +29,7 @@ export interface BookletsStarted {
   code: string;
   bookletname: string;
   locked: boolean;
+  laststart: Date;
 }
 
 export interface UnitResponse {
@@ -52,6 +53,8 @@ export interface MonitorData {
   bookletsPrepared: number;
   bookletsStarted: number;
   bookletsLocked: number;
+  laststart: Date;
+  laststartStr: string;
 }
 
 export interface ResultData {
@@ -60,6 +63,7 @@ export interface ResultData {
   num_units_min: number;
   num_units_max: number;
   num_units_mean: number;
+  lastchange: number;
 }
 
 export interface LogData {
diff --git a/src/app/workspace/workspacedata.service.ts b/src/app/workspace/workspacedata.service.ts
index 3aee6f83..97787c59 100644
--- a/src/app/workspace/workspacedata.service.ts
+++ b/src/app/workspace/workspacedata.service.ts
@@ -23,7 +23,32 @@ export class WorkspaceDataService {
   public get ws() : number {
     return this.workspaceId$.getValue();
   }
+  private _wsRole = '';
+  public get wsRole() : string {
+    return this._wsRole;
+  }
+  private _wsName = '';
+  public get wsName() : string {
+    return this._wsName;
+  }
+  public navLinks = [];
 
+  // .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
+  private navLinksRW = [
+    {path: 'files', label: 'Dateien'},
+    //  {path: 'syscheck', label: 'System-Check Berichte'},
+    {path: 'monitor', label: 'Monitor'},
+    {path: 'results', label: 'Ergebnisse'}
+  ];
+  private navLinksRO = [
+    {path: 'files', label: 'Dateien'},
+    // {path: 'syscheck', label: 'System-Check Berichte'},
+    {path: 'monitor', label: 'Monitor'},
+    {path: 'results', label: 'Ergebnisse'}
+  ];
+  private navLinksMO = [
+    {path: 'monitor', label: 'Monitor'}
+  ];
 
   // ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
   constructor (
@@ -66,7 +91,27 @@ export class WorkspaceDataService {
   }
 
   // *******************************************************************************************************
-  setWorkspaceId(newId: number) {
+  setWorkspace(newId: number, newRole: string, newName: string) {
+    this._wsName = newName;
+    this._wsRole = newRole;
+    switch (newRole.toUpperCase()) {
+      case 'RW': {
+        this.navLinks = this.navLinksRW;
+        break;
+      }
+      case 'RO': {
+        this.navLinks = this.navLinksRO;
+        break;
+      }
+      case 'MO': {
+        this.navLinks = this.navLinksMO;
+        break;
+      }
+      default: {
+        this.navLinks = [];
+        break;
+      }
+    }    
     this.workspaceId$.next(newId);
   }
 }
-- 
GitLab