From ab33cba9a064d86f73db6d9d7f20225a2cb2acf3 Mon Sep 17 00:00:00 2001
From: Alex ORLUC <alex.orluc@maarch.org>
Date: Fri, 7 May 2021 17:59:05 +0200
Subject: [PATCH] FEAT #16982 TIME 4 WIP otp connectors admin

---
 lang/fr.json                                  |  8 ++-
 .../app/administration/otp/otp.component.html | 57 ++++++++++++++---
 .../app/administration/otp/otp.component.ts   | 63 +++++++++++++++----
 .../otps/otp-create.component.html            | 12 ++--
 .../otps/otp-create.component.scss            |  5 +-
 .../otps/yousign/otp-yousign.component.html   |  7 +--
 .../visa-workflow/visa-workflow.component.ts  |  2 +-
 .../app/service/notification.service.ts       | 10 +--
 8 files changed, 125 insertions(+), 39 deletions(-)

diff --git a/lang/fr.json b/lang/fr.json
index 1a14a820bd..069aaa9222 100755
--- a/lang/fr.json
+++ b/lang/fr.json
@@ -486,9 +486,15 @@
 		"otpMsg": "L'utilisateur sera notifié par <b>mail</b> et recevra un <b>code de sécurité</b> par <b>{{security}}</b> au moment de son tour dans le circuit.",
 		"manage_otp_connectorsAdmin": "Administrer les connecteurs OTP",
 		"manage_otp_connectors": "Connecteurs OTP",
+		"manage_otp_connectorsDesc": "Administrer les différents connecteurs de parapheurs externes pour des utilisateurs OTP.",
 		"newConnector": "Nouveau connecteur",
 		"connectors": "connecteur(s)",
 		"type": "Type",
-		"otpConnectorCreation": "Création d'un connecteur OTP"
+		"otpConnectorCreation": "Création d'un connecteur OTP",
+		"apiKey": "Clé API",
+		"apiUri": "Adresse de l'API",
+		"connectorAdded": "Connecteur ajouté",
+		"connectorUpdated": "Connecteur modifié",
+		"connectorDeleted": "Connecteur supprimé"
 	}
 }
diff --git a/src/frontend/app/administration/otp/otp.component.html b/src/frontend/app/administration/otp/otp.component.html
index f2d47e1fa5..3042baca6f 100644
--- a/src/frontend/app/administration/otp/otp.component.html
+++ b/src/frontend/app/administration/otp/otp.component.html
@@ -9,17 +9,60 @@
 </ion-header>
 <form style="display: contents;" id="adminForm" (ngSubmit)="onSubmit()" #adminForm="ngForm">
     <ion-content>
-        <ion-item>
-            <ion-label color="secondary">{{'lang.source' | translate}}</ion-label>
-            <ion-select name="type" [(ngModel)]="connector.type" [value]="connector.type" cancelText="{{'lang.cancel' | translate}}">
-                <ion-select-option *ngFor="let connector of otpService.getConnectorTypes()" [value]="connector">
-                    {{connector | translate}}</ion-select-option>
-            </ion-select>
-        </ion-item>
         <ion-item>
             <ion-label color="secondary" position="floating">{{'lang.label' | translate}} *</ion-label>
             <ion-input name="label" [maxlength]="128" [(ngModel)]="connector.label" required>
             </ion-input>
         </ion-item>
+        <ion-list>
+            <ion-radio-group name="type" [(ngModel)]="connector.type" [value]="connector.type">
+                <ion-list-header>
+                    <ion-label color="secondary">{{'lang.source' | translate}} *</ion-label>
+                </ion-list-header>
+                <ion-item *ngFor="let connector of connectorTypes">
+                    <ion-radio slot="start" [value]="connector.id"></ion-radio>
+                    <ion-avatar slot="start">
+                        <img [src]="connector.logo">
+                      </ion-avatar>
+                    <ion-label>
+                        {{connector.id | translate}}
+                    </ion-label>
+                </ion-item>
+            </ion-radio-group>
+        </ion-list>
+        <ion-item>
+            <ion-label color="secondary" position="floating">{{'lang.apiUri' | translate}} *</ion-label>
+            <ion-input name="apiUri" [maxlength]="128" [(ngModel)]="connector.apiUri" required>
+            </ion-input>
+        </ion-item>
+        <ion-item>
+            <ion-label color="secondary" position="floating">{{'lang.apiKey' | translate}} *</ion-label>
+            <ion-input name="apiKey" [maxlength]="128" [(ngModel)]="connector.apiKey" required>
+            </ion-input>
+        </ion-item>
+        <ion-list>
+            <ion-list-header>
+                <ion-label color="secondary">
+                    {{'lang.securityCodeSendMode' | translate}} *
+                </ion-label>
+            </ion-list-header>
+            <ion-item *ngFor="let mode of securityModes">
+                <ion-label>{{mode}}</ion-label>
+                <ion-checkbox slot="start" [checked]="isCheckedSecurityMode(mode)"
+                    (ionChange)="toggleSecurityMode($event.detail)" [value]="mode"></ion-checkbox>
+            </ion-item>
+        </ion-list>
+        <ion-item text-center lines="none" style="position: sticky;bottom:0px;z-index:1;">
+            <div style="display: flex;align-items: center;justify-content: center;width: 100%;background: white;">
+                <ion-button type="submit" shape="round" size="large" fill="outline" color="primary"
+                    [disabled]="!adminForm.form.valid || connector.securityModes.length === 0">
+                    <ion-label style="font-size: 13px;">{{'lang.validate' | translate}}</ion-label>
+                </ion-button>
+                <ion-button *ngIf="!creationMode" type="button" shape="round" size="large" fill="outline" color="danger"
+                    (click)="deleteconnector()">
+                    <ion-label style="font-size: 13px;">{{'lang.delete' | translate}}</ion-label>
+                </ion-button>
+            </div>
+        </ion-item>
     </ion-content>
 </form>
\ No newline at end of file
diff --git a/src/frontend/app/administration/otp/otp.component.ts b/src/frontend/app/administration/otp/otp.component.ts
index 604a431379..f4e9822f71 100644
--- a/src/frontend/app/administration/otp/otp.component.ts
+++ b/src/frontend/app/administration/otp/otp.component.ts
@@ -17,6 +17,7 @@ export interface Connector {
     label: string;
     apiUri: string;
     apiKey: string;
+    securityModes: string[];
 }
 
 @Component({
@@ -33,6 +34,16 @@ export class OtpComponent implements OnInit {
     connectorClone: Connector;
     title: string = '';
 
+    apiDefaultUri = {
+        yousign : 'https://api.yousign.com/'
+    };
+
+    securityModes: string[] =  [
+        'sms',
+        'email'
+    ];
+
+    connectorTypes: any[] = [];
 
     constructor(
         public http: HttpClient,
@@ -48,14 +59,15 @@ export class OtpComponent implements OnInit {
         public alertController: AlertController,
         public otpService: OtpService,
     ) {
+        this.getConnectorTypes();
         this.connector = {
             id: '',
-            type: this.otpService.getConnectorTypes()[0],
+            type: this.connectorTypes[0].id,
             label: '',
-            apiUri: '',
-            apiKey: ''
+            apiUri: this.apiDefaultUri[this.connectorTypes[0]],
+            apiKey: '',
+            securityModes: ['sms']
         };
-        this.connectorClone = JSON.parse(JSON.stringify(this.connector));
     }
 
     ngOnInit(): void {
@@ -68,7 +80,8 @@ export class OtpComponent implements OnInit {
             } else {
                 this.creationMode = false;
 
-                this.http.get('../rest/otps/' + params['id'])
+                // WAIT BACK
+                /* this.http.get('../rest/otps/' + params['id'])
                     .pipe(
                         map((data: any) => data.otp),
                         finalize(() => {
@@ -84,11 +97,22 @@ export class OtpComponent implements OnInit {
                             return of(false);
                         })
                     )
-                    .subscribe();
+                    .subscribe();*/
             }
         });
     }
 
+    getConnectorTypes() {
+        this.connectorTypes = this.otpService.getConnectorTypes().map((item: any) => ({
+            id: item,
+            logo: null
+        }));
+
+        this.connectorTypes.forEach(async element => {
+            element.logo = await this.otpService.getUserOtpIcon(element.id);
+        });
+    }
+
     canValidate() {
         if (this.connector.label === this.connectorClone.label) {
             return false;
@@ -107,7 +131,8 @@ export class OtpComponent implements OnInit {
 
     modifyconnector() {
         this.loading = true;
-        this.http.put('../rest/connectors/' + this.connector.id, this.connector)
+        // WAIT BACK
+        /* this.http.put('../rest/connectors/' + this.connector.id, this.connector)
             .pipe(
                 tap(() => {
                     this.router.navigate(['/administration/connectors']);
@@ -118,12 +143,12 @@ export class OtpComponent implements OnInit {
                     return of(false);
                 })
             )
-            .subscribe();
+            .subscribe();*/
     }
 
     createconnector() {
         this.loading = true;
-        this.http.post('../rest/connectors', this.connector)
+        /* this.http.post('../rest/connectors', this.connector)
             .pipe(
                 tap((data: any) => {
                     this.router.navigate(['/administration/connectors/' + data.id]);
@@ -134,7 +159,7 @@ export class OtpComponent implements OnInit {
                     return of(false);
                 })
             )
-            .subscribe();
+            .subscribe(); */
     }
 
     async deleteconnector() {
@@ -151,7 +176,8 @@ export class OtpComponent implements OnInit {
                 {
                     text: this.translate.instant('lang.yes'),
                     handler: () => {
-                        this.http.delete('../rest/connectors/' + this.connector.id)
+                        // WAIT BACK
+                        /* this.http.delete('../rest/connectors/' + this.connector.id)
                             .pipe(
                                 tap(() => {
                                     this.router.navigate(['/administration/connectors']);
@@ -162,7 +188,7 @@ export class OtpComponent implements OnInit {
                                     return of(false);
                                 })
                             )
-                            .subscribe();
+                            .subscribe(); */
                     }
                 }
             ]
@@ -171,6 +197,19 @@ export class OtpComponent implements OnInit {
         await alert.present();
     }
 
+    toggleSecurityMode(ev: any) {
+        if (ev.checked) {
+            this.connector.securityModes.push(ev.value);
+        } else {
+            const index = this.connector.securityModes.indexOf(ev.value);
+            this.connector.securityModes.splice(index, 1);
+        }
+    }
+
+    isCheckedSecurityMode(value: string) {
+        return this.connector.securityModes.indexOf(value) > -1;
+    }
+
     cancel() {
         this.router.navigate(['/administration/connectors']);
     }
diff --git a/src/frontend/app/document/visa-workflow/otps/otp-create.component.html b/src/frontend/app/document/visa-workflow/otps/otp-create.component.html
index 3d7f1e33c5..07f330525b 100644
--- a/src/frontend/app/document/visa-workflow/otps/otp-create.component.html
+++ b/src/frontend/app/document/visa-workflow/otps/otp-create.component.html
@@ -9,12 +9,7 @@
     </ion-toolbar>
 </ion-header>
 <ion-content>
-    <ion-card>
-        <ion-item color="primary">
-            <ion-label class="info" [innerHTML]="'lang.otpMsg' | translate : { security : appOtpYousign?.getSecurityMode()}"></ion-label>
-        </ion-item>
-    </ion-card>
-    <ion-card>
+    <ion-card *ngIf="sources.length > 1">
         <ion-item>
             <ion-label color="secondary">{{'lang.source' | translate}}</ion-label>
             <ion-select [value]="currentSource.id" cancelText="{{'lang.cancel' | translate}}">
@@ -24,6 +19,11 @@
         </ion-item>
     </ion-card>
     <app-otp-yousign #appOtpYousign *ngIf="currentSource.type === 'yousign'"></app-otp-yousign>
+    <ion-card>
+        <ion-item color="primary">
+            <ion-label class="info" [innerHTML]="'lang.otpMsg' | translate : { security : appOtpYousign?.getSecurityMode()}"></ion-label>
+        </ion-item>
+    </ion-card>
 </ion-content>
 <ion-footer class="ion-no-border">
     <ion-toolbar>
diff --git a/src/frontend/app/document/visa-workflow/otps/otp-create.component.scss b/src/frontend/app/document/visa-workflow/otps/otp-create.component.scss
index 806b07f733..99a474645b 100644
--- a/src/frontend/app/document/visa-workflow/otps/otp-create.component.scss
+++ b/src/frontend/app/document/visa-workflow/otps/otp-create.component.scss
@@ -1,5 +1,6 @@
-.my-custom-class {
-    --min-width: 400px;
+::ng-deep .custom-modal .modal-wrapper {
+  height: 650px;
+  max-height: 100%;
 }
 
 .info {
diff --git a/src/frontend/app/document/visa-workflow/otps/yousign/otp-yousign.component.html b/src/frontend/app/document/visa-workflow/otps/yousign/otp-yousign.component.html
index 979fb5fb5c..2d7b206e04 100644
--- a/src/frontend/app/document/visa-workflow/otps/yousign/otp-yousign.component.html
+++ b/src/frontend/app/document/visa-workflow/otps/yousign/otp-yousign.component.html
@@ -37,13 +37,8 @@
                 </ion-item>
             </div>
         </ion-radio-group>
-        <!--<ion-label color="secondary" position="floating">{{'lang.role' | translate}}</ion-label>
-        <ion-select name="mode" [(ngModel)]="otp.role" [value]="otp.role" cancelText="{{'lang.cancel' | translate}}">
-            <ion-select-option *ngFor="let role of roles" [value]="role">
-                {{'lang.' + role | translate}}</ion-select-option>
-        </ion-select>-->
     </ion-list>
-    <ion-list>
+    <ion-list *ngIf="securityModes.length > 1">
         <ion-item>
             <ion-label color="secondary" position="floating">{{'lang.securityCodeSendMode' | translate}} *</ion-label>
             <ion-select name="mode" [(ngModel)]="otp.security" [value]="otp.security"
diff --git a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts
index 45299a66c2..2ab89ef5c3 100644
--- a/src/frontend/app/document/visa-workflow/visa-workflow.component.ts
+++ b/src/frontend/app/document/visa-workflow/visa-workflow.component.ts
@@ -151,7 +151,7 @@ export class VisaWorkflowComponent implements OnInit {
 
     async openOtpModal(ev: any) {
         const modal = await this.modalController.create({
-            cssClass: 'my-custom-class',
+            cssClass: 'custom-modal',
             component: OtpCreateComponent,
             backdropDismiss: false,
         });
diff --git a/src/frontend/app/service/notification.service.ts b/src/frontend/app/service/notification.service.ts
index 0c91ed3ee2..54d66710bb 100644
--- a/src/frontend/app/service/notification.service.ts
+++ b/src/frontend/app/service/notification.service.ts
@@ -50,10 +50,12 @@ export class NotificationService {
                 }
             } else if (err.error.exception !== undefined) {
                 this.error(err.error.exception[0].message);
-            } else if (err.error.error !== undefined && err.error.error.message !== undefined) {
-                this.error(err.error.error.message);
-            } else if (err.error.error[0] !== undefined) {
-                this.error(err.error.error[0].message);
+            } else if (err.error.error !== undefined) {
+                if (err.error.error.message !== undefined) {
+                    this.error(err.error.error.message);
+                } else if (err.error.error[0].message !== undefined) {
+                    this.error(err.error.error[0].message);
+                }
             } else {
                 this.error(err.message);
             }
-- 
GitLab