From bf167040c5e55256a58d670104bfb90df635ccb7 Mon Sep 17 00:00:00 2001
From: Alex ORLUC <alex.orluc@maarch.org>
Date: Fri, 26 Jun 2020 18:15:30 +0200
Subject: [PATCH] FEAT #13671 TIME 5:30 add continue fail install  + some fixes

---
 .../customization.component.html              |  4 +-
 .../customization/customization.component.ts  | 34 ++++++++---
 .../installer/database/database.component.ts  | 18 +++---
 .../docservers/docservers.component.ts        | 24 ++++----
 .../install-action.component.html             | 36 ++++++++----
 .../install-action.component.scss             | 18 +++++-
 .../install-action.component.ts               | 58 +++++++++++++------
 .../app/installer/installer.component.html    | 28 ++++-----
 .../app/installer/installer.component.ts      |  4 +-
 .../app/installer/installer.module.ts         |  4 +-
 .../app/installer/installer.service.ts        | 18 ++++++
 .../mailserver/mailserver.component.ts        | 30 ++++++++--
 .../prerequisite/prerequisite.component.html  |  2 +-
 .../prerequisite/prerequisite.component.ts    |  4 +-
 src/frontend/app/installer/types.ts           |  1 +
 .../useradmin/useradmin.component.html        |  5 --
 .../useradmin/useradmin.component.ts          | 20 +++++--
 src/frontend/lang/lang-fr.ts                  |  4 ++
 18 files changed, 213 insertions(+), 99 deletions(-)
 create mode 100644 src/frontend/app/installer/installer.service.ts

diff --git a/src/frontend/app/installer/customization/customization.component.html b/src/frontend/app/installer/customization/customization.component.html
index 2466edda1ee..e9e6b8fde33 100644
--- a/src/frontend/app/installer/customization/customization.component.html
+++ b/src/frontend/app/installer/customization/customization.component.html
@@ -45,12 +45,12 @@
                     [class.selected]="stepFormGroup.controls['bodyLoginBackground'].value === 'bodylogin.jpg'"
                     style="background:url(assets/bodylogin.jpg);background-size: cover;">
                 </mat-card>
-                <mat-card *ngFor="let background of backgroundList" (click)="stepFormGroup.controls['bodyLoginBackground'].setValue(background.filename)"
+                <mat-card *ngFor="let background of backgroundList" (click)="stepFormGroup.controls['bodyLoginBackground'].setValue(background.url)"
                     style="opacity: 0.3;" class="backgroundItem"
                     [class.selected]="background.filename === stepFormGroup.controls['bodyLoginBackground'].value"
                     [style.background]="'url('+background.url+')'">
                 </mat-card>
-                <mat-card style="opacity: 0.3;display: flex;align-items: center;justify-content: center;" class="backgroundItem" (click)="uploadFile.click()">
+                <mat-card style="opacity: 0.3;display: flex;align-items: center;justify-content: center;" class="backgroundItem" [class.readonly]="readonlyState" (click)="uploadFile.click()">
                     <input type="file" name="files[]" #uploadFile (change)="uploadTrigger($event, 'bg')" accept="image/jpg" style="display: none;">
                     <i class="fa fa-plus" style="font-size: 30px;color: #666;"></i>
                 </mat-card>
diff --git a/src/frontend/app/installer/customization/customization.component.ts b/src/frontend/app/installer/customization/customization.component.ts
index 09565cab12a..c9292ebd317 100644
--- a/src/frontend/app/installer/customization/customization.component.ts
+++ b/src/frontend/app/installer/customization/customization.component.ts
@@ -8,6 +8,7 @@ import { environment } from '../../../environments/environment';
 import { ScanPipe } from 'ngx-pipes';
 import { debounceTime, filter, tap } from 'rxjs/operators';
 import { HttpClient } from '@angular/common/http';
+import { InstallerService } from '../installer.service';
 
 declare var tinymce: any;
 
@@ -20,6 +21,7 @@ declare var tinymce: any;
 export class CustomizationComponent implements OnInit {
     lang: any = LANG;
     stepFormGroup: FormGroup;
+    readonlyState: boolean = false;
 
     backgroundList: any[] = [];
 
@@ -29,6 +31,7 @@ export class CustomizationComponent implements OnInit {
         private sanitizer: DomSanitizer,
         private scanPipe: ScanPipe,
         public http: HttpClient,
+        private installerService: InstallerService
     ) {
         const valIdentifier: ValidatorFn[] = [Validators.pattern(/^[a-zA-Z0-9_\-]*$/), Validators.required];
 
@@ -67,9 +70,19 @@ export class CustomizationComponent implements OnInit {
     }
 
     initStep() {
-        this.initMce();
-        // this.checkCustomExist();
-        return false;
+        if (this.installerService.isStepAlreadyLaunched('createCustom') && this.installerService.isStepAlreadyLaunched('customization')) {
+            this.stepFormGroup.disable();
+            this.readonlyState = true;
+            this.initMce(true);
+        } else if (this.installerService.isStepAlreadyLaunched('createCustom')) {
+            this.stepFormGroup.controls['customId'].disable();
+            this.stepFormGroup.controls['appName'].disable();
+            this.readonlyState = true;
+            this.initMce(true);
+        } else {
+            this.readonlyState = false;
+            this.initMce();
+        }
     }
 
     checkCustomExist() {
@@ -89,7 +102,7 @@ export class CustomizationComponent implements OnInit {
     }
 
     isValidStep() {
-        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid;
+        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid || (this.installerService.isStepAlreadyLaunched('createCustom') && this.installerService.isStepAlreadyLaunched('customization'));
     }
 
 
@@ -97,7 +110,7 @@ export class CustomizationComponent implements OnInit {
         return this.stepFormGroup;
     }
 
-    initMce() {
+    initMce(readonly = false) {
         tinymce.init({
             selector: 'textarea',
             base_url: '../node_modules/tinymce/',
@@ -107,6 +120,7 @@ export class CustomizationComponent implements OnInit {
             language_url: `../node_modules/tinymce-i18n/langs/${LANG.langISO.replace('-', '_')}.js`,
             menubar: false,
             statusbar: false,
+            readonly : readonly,
             plugins: [
                 'autolink'
             ],
@@ -115,15 +129,16 @@ export class CustomizationComponent implements OnInit {
             },
             toolbar_sticky: true,
             toolbar_drawer: 'floating',
-            toolbar: 'undo redo | fontselect fontsizeselect | bold italic underline strikethrough forecolor | maarch_b64image | \
+            toolbar: !readonly ? 'undo redo | fontselect fontsizeselect | bold italic underline strikethrough forecolor | maarch_b64image | \
         alignleft aligncenter alignright alignjustify \
-        bullist numlist outdent indent | removeformat'
+        bullist numlist outdent indent | removeformat' : ''
         });
     }
 
     getInfoToInstall(): StepAction[] {
         return [
             {
+                idStep : 'createCustom',
                 body: {
                     customId: this.stepFormGroup.controls['customId'].value,
                     applicationName: this.stepFormGroup.controls['appName'].value,
@@ -133,10 +148,11 @@ export class CustomizationComponent implements OnInit {
                 installPriority: 1
             },
             {
+                idStep : 'customization',
                 body: {
                     loginMessage: this.stepFormGroup.controls['loginMessage'].value,
                     homeMessage: this.stepFormGroup.controls['homeMessage'].value,
-                    bodyLoginBackground: this.stepFormGroup.controls['bodyLoginBackground'].value,
+                    bodyLogin: this.stepFormGroup.controls['bodyLoginBackground'].value,
                     logo: this.stepFormGroup.controls['uploadedLogo'].value,
                 },
                 description: this.lang.stepCustomizationActionDesc,
@@ -147,7 +163,7 @@ export class CustomizationComponent implements OnInit {
     }
 
     uploadTrigger(fileInput: any, mode: string) {
-        if (fileInput.target.files && fileInput.target.files[0]) {
+        if (fileInput.target.files && fileInput.target.files[0] && !this.readonlyState) {
             const allowedExtension = mode !== 'logo' ? ['image/jpg'] : ['image/svg+xml'];
             if (allowedExtension.indexOf(fileInput.target.files[0].type) !== -1) {
                 const reader = new FileReader();
diff --git a/src/frontend/app/installer/database/database.component.ts b/src/frontend/app/installer/database/database.component.ts
index 2d2f2c26aac..48cfeaf227f 100644
--- a/src/frontend/app/installer/database/database.component.ts
+++ b/src/frontend/app/installer/database/database.component.ts
@@ -8,6 +8,7 @@ import { of } from 'rxjs/internal/observable/of';
 import { LANG } from '../../translate.component';
 import { StepAction } from '../types';
 import { FunctionsService } from '../../../service/functions.service';
+import { InstallerService } from '../installer.service';
 
 @Component({
     selector: 'app-database',
@@ -29,6 +30,7 @@ export class DatabaseComponent implements OnInit {
         private _formBuilder: FormBuilder,
         private notify: NotificationService,
         private functionsService: FunctionsService,
+        private installerService: InstallerService
     ) {
         this.stepFormGroup = this._formBuilder.group({
             dbHostCtrl: ['localhost', Validators.required],
@@ -78,7 +80,9 @@ export class DatabaseComponent implements OnInit {
     }
 
     initStep() {
-        return false;
+        if (this.installerService.isStepAlreadyLaunched('database')) {
+            this.stepFormGroup.disable();
+        }
     }
 
     checkConnection() {
@@ -116,16 +120,7 @@ export class DatabaseComponent implements OnInit {
     }
 
     isValidStep() {
-        /*Object.keys(this.stepFormGroup.controls).forEach(key => {
-
-            const controlErrors: ValidationErrors = this.stepFormGroup.get(key).errors;
-            if (controlErrors != null) {
-                Object.keys(controlErrors).forEach(keyError => {
-                    console.log('Key control: ' + key + ', keyError: ' + keyError + ', err value: ', controlErrors[keyError]);
-                });
-            }
-        });*/
-        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid;
+        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid || this.installerService.isStepAlreadyLaunched('database');
     }
 
     isEmptyConnInfo() {
@@ -142,6 +137,7 @@ export class DatabaseComponent implements OnInit {
 
     getInfoToInstall(): StepAction[] {
         return [{
+            idStep : 'database',
             body: {
                 server: this.stepFormGroup.controls['dbHostCtrl'].value,
                 port: this.stepFormGroup.controls['dbPortCtrl'].value,
diff --git a/src/frontend/app/installer/docservers/docservers.component.ts b/src/frontend/app/installer/docservers/docservers.component.ts
index eb553b0b1a7..67a553a24ef 100644
--- a/src/frontend/app/installer/docservers/docservers.component.ts
+++ b/src/frontend/app/installer/docservers/docservers.component.ts
@@ -3,9 +3,10 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { NotificationService } from '../../../service/notification/notification.service';
 import { tap } from 'rxjs/internal/operators/tap';
 import { LANG } from '../../translate.component';
-import { catchError } from 'rxjs/operators';
-import { of } from 'rxjs';
 import { HttpClient } from '@angular/common/http';
+import { catchError } from 'rxjs/internal/operators/catchError';
+import { of } from 'rxjs/internal/observable/of';
+import { InstallerService } from '../installer.service';
 
 @Component({
     selector: 'app-docservers',
@@ -20,6 +21,7 @@ export class DocserversComponent implements OnInit {
         private _formBuilder: FormBuilder,
         private notify: NotificationService,
         public http: HttpClient,
+        private installerService: InstallerService
     ) {
         this.stepFormGroup = this._formBuilder.group({
             docserversPath: ['/opt/maaarch/docservers/', Validators.required],
@@ -36,14 +38,15 @@ export class DocserversComponent implements OnInit {
 
 
     isValidStep() {
-        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid;
+        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid || this.installerService.isStepAlreadyLaunched('docserver');
     }
 
     initStep() {
-        return false;
+        if (this.installerService.isStepAlreadyLaunched('docserver')) {
+            this.stepFormGroup.disable();
+        }
     }
 
-
     getFormGroup() {
         return this.stepFormGroup;
     }
@@ -53,25 +56,22 @@ export class DocserversComponent implements OnInit {
             path: this.stepFormGroup.controls['docserversPath'].value,
         };
 
-        /*this.http.get(`../rest/installer/docservers`, { params: info }).pipe(
+        this.http.get(`../rest/installer/docservers`, { params: info }).pipe(
             tap((data: any) => {
                 this.notify.success(this.lang.rightInformations);
                 this.stepFormGroup.controls['stateStep'].setValue('success');
             }),
             catchError((err: any) => {
-                this.notify.error(this.lang.badInformations);
+                this.notify.error(this.lang.pathUnreacheable);
                 this.stepFormGroup.controls['stateStep'].setValue('');
                 return of(false);
             })
-        ).subscribe();*/
-
-        // FOR TEST
-        this.stepFormGroup.controls['stateStep'].setValue('success');
-        this.notify.success('TODO : waiting back docservers check');
+        ).subscribe();
     }
 
     getInfoToInstall(): any[] {
         return [{
+            idStep : 'docserver',
             body: {
                 path: this.stepFormGroup.controls['docserversPath'].value,
             },
diff --git a/src/frontend/app/installer/install-action/install-action.component.html b/src/frontend/app/installer/install-action/install-action.component.html
index 39a18ad46a1..747448a795f 100644
--- a/src/frontend/app/installer/install-action/install-action.component.html
+++ b/src/frontend/app/installer/install-action/install-action.component.html
@@ -1,21 +1,33 @@
 <div class="mat-dialog-content-container">
     <div mat-dialog-content>
-        <mat-list-item *ngFor="let step of steps">
-            <div mat-line class="step" [class.endStep]="step.state==='OK' || step.state==='KO'"
-                [class.currentStep]="step.state==='inProgress'">{{step.label}} <ng-container
-                    *ngIf="step.state==='inProgress'">...</ng-container>
-                <i *ngIf="step.state==='OK'" class="fa fa-check" style="color: green"></i>
-                <i *ngIf="step.state==='KO'" class="fa fa-times" style="color: red"></i>
-                <div *ngIf="step.msgErr!==''" class="alert-message alert-message-danger" role="alert" style="margin-top: 30px;min-width: 100%;">
-                    {{step.msgErr}}
-                </div>
-            </div>
-        </mat-list-item>
+        <mat-accordion>
+            <mat-expansion-panel hideToggle expanded>
+                <h2 class="text-center">Vous y êtes presque !</h2>
+                <button mat-raised-button type="button" color="primary" (click)="installStepAction.open();launchInstall()" style="font-size: 25px;padding: 20px;">
+                    <i class=" far fa-hdd"></i> {{lang.launchInstall}}
+                </button>
+            </mat-expansion-panel>
+            <mat-expansion-panel #installStepAction [expanded]="false">
+                <mat-list-item *ngFor="let step of steps">
+                    <div mat-line class="step" [class.endStep]="step.state==='OK' || step.state==='KO'"
+                        [class.currentStep]="step.state==='inProgress'"><span class="stepLabel">{{step.label}}</span> <ng-container
+                            *ngIf="step.state==='inProgress'">...</ng-container>&nbsp;
+                        <i *ngIf="step.state==='OK'" class="fa fa-check" style="color: green"></i>
+                        <i *ngIf="step.state==='KO'" class="fa fa-times" style="color: red"></i>
+                        <div *ngIf="step.msgErr!==''" class="alert-message alert-message-danger" role="alert"
+                            style="margin-top: 30px;min-width: 100%;">
+                            {{step.msgErr}}
+                        </div>
+                    </div>
+                </mat-list-item>
+            </mat-expansion-panel>
+        </mat-accordion>
     </div>
     <ng-container *ngIf="isInstallComplete()">
         <span class="divider-modal"></span>
         <div mat-dialog-actions class="actions">
-            <button *ngIf="!isInstallError()" mat-raised-button mat-button color="primary" [mat-dialog-close]="">Accéder à la nouvelle instance</button>
+            <button *ngIf="!isInstallError()" mat-raised-button mat-button color="primary" [mat-dialog-close]="">Accéder
+                à la nouvelle instance</button>
             <button *ngIf="isInstallError()" mat-raised-button mat-button [mat-dialog-close]="">{{lang.cancel}}</button>
         </div>
     </ng-container>
diff --git a/src/frontend/app/installer/install-action/install-action.component.scss b/src/frontend/app/installer/install-action/install-action.component.scss
index d694d35462f..aebd4c82dba 100644
--- a/src/frontend/app/installer/install-action/install-action.component.scss
+++ b/src/frontend/app/installer/install-action/install-action.component.scss
@@ -1,14 +1,30 @@
 .step {
     opacity: 0.5;
     transition: all 0.2s;
+    .stepLabel{
+        transition: all 0.2s;
+    }
 }
 
 .currentStep {
     opacity: 1;
-    font-size: 150%;
+    .stepLabel{
+        font-size: 150%;
+        transition: all 0.2s;
+    }
+    
     transition: all 0.2s;
 }
 
 .endStep {
     opacity: 1;
+}
+
+.mat-expansion-panel {
+    box-shadow: none;
+}
+
+.mat-expansion-panel-body {
+    display: flex;
+    flex-direction: column;
 }
\ No newline at end of file
diff --git a/src/frontend/app/installer/install-action/install-action.component.ts b/src/frontend/app/installer/install-action/install-action.component.ts
index fdf31128a58..108e6b36a38 100644
--- a/src/frontend/app/installer/install-action/install-action.component.ts
+++ b/src/frontend/app/installer/install-action/install-action.component.ts
@@ -1,26 +1,43 @@
-import { Component, OnInit, Inject } from '@angular/core';
+import { Component, OnInit, Inject, AfterViewInit } from '@angular/core';
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { LANG } from '../../../app/translate.component';
 import { HttpClient } from '@angular/common/http';
 import { tap } from 'rxjs/internal/operators/tap';
 import { catchError } from 'rxjs/internal/operators/catchError';
 import { of } from 'rxjs/internal/observable/of';
+import { InstallerService } from '../installer.service';
 
 @Component({
     selector: 'app-install-action',
     templateUrl: './install-action.component.html',
     styleUrls: ['./install-action.component.scss']
 })
-export class InstallActionComponent implements OnInit {
+export class InstallActionComponent implements OnInit, AfterViewInit {
     lang: any = LANG;
     steps: any[] = [];
     customId: string = '';
 
-    constructor(@Inject(MAT_DIALOG_DATA) public data: any, public dialogRef: MatDialogRef<InstallActionComponent>, public http: HttpClient) { }
+    // Workaround for angular component issue #13870
+    disableAnimation = true;
+
+
+    constructor(
+        @Inject(MAT_DIALOG_DATA) public data: any,
+        public dialogRef: MatDialogRef<InstallActionComponent>,
+        public http: HttpClient,
+        private installerService: InstallerService
+    ) { }
 
     async ngOnInit(): Promise<void> {
         this.initSteps();
 
+    }
+
+    ngAfterViewInit(): void {
+        setTimeout(() => this.disableAnimation = false);
+    }
+
+    async launchInstall() {
         for (let index = 0; index < this.data.length; index++) {
             this.steps[index].state = 'inProgress';
             await this.doStep(index);
@@ -36,6 +53,7 @@ export class InstallActionComponent implements OnInit {
             }
             this.steps.push(
                 {
+                    idStep : step.idStep,
                     label: step.description,
                     state: '',
                     msgErr: '',
@@ -47,26 +65,32 @@ export class InstallActionComponent implements OnInit {
     doStep(index: number) {
         return new Promise((resolve, reject) => {
             console.log(this.steps[index]);
-            this.http.post(this.data[index].route, this.data[index].body).pipe(
-                tap((data: any) => {
-                    this.steps[index].state = 'OK';
-                    resolve(true);
-                }),
-                catchError((err: any) => {
-                    this.steps[index].state = 'KO';
-                    resolve(true);
-                    this.steps[index].msgErr = err.error.errors;
-                    return of(false);
-                })
-            ).subscribe();
+            if (this.installerService.isStepAlreadyLaunched(this.data[index].idStep)) {
+                this.steps[index].state = 'OK';
+                resolve(true);
+            } else {
+                this.http.post(this.data[index].route, this.data[index].body).pipe(
+                    tap((data: any) => {
+                        this.steps[index].state = 'OK';
+                        this.installerService.setStep(this.steps[index]);
+                        resolve(true);
+                    }),
+                    catchError((err: any) => {
+                        this.steps[index].state = 'KO';
+                        resolve(true);
+                        this.steps[index].msgErr = err.error.errors;
+                        return of(false);
+                    })
+                ).subscribe();
+            }
         });
     }
 
     isInstallComplete() {
-        return this.steps.filter(step => step.state === '' ).length === 0;
+        return this.steps.filter(step => step.state === '').length === 0;
     }
 
     isInstallError() {
-        return this.steps.filter(step => step.state === 'KO' ).length > 0;
+        return this.steps.filter(step => step.state === 'KO').length > 0;
     }
 }
diff --git a/src/frontend/app/installer/installer.component.html b/src/frontend/app/installer/installer.component.html
index cd3aa4022e0..183b774ef30 100644
--- a/src/frontend/app/installer/installer.component.html
+++ b/src/frontend/app/installer/installer.component.html
@@ -7,20 +7,6 @@
         <div class="container" [class.fullContainer]="appService.getViewMode()">
             <div class="container-content" style="overflow: hidden;">
                 <mat-horizontal-stepper [@.disabled]="true" linear #stepper style="height: 100vh;overflow: auto;" (selectionChange)="initStep($event)">
-                    <mat-step [stepControl]="appMailserver.getFormGroup()">
-                        <ng-template matStepLabel>{{lang.stepMailServer}}</ng-template>
-                        <div class="stepContainer">
-                            <div class="stepContent">
-                                <app-mailserver #appMailserver #stepContent (tiggerInstall)="endInstall()"></app-mailserver>
-                            </div>
-                            <button mat-fab matStepperPrevious class="previousStepButton" color="primary">
-                                <mat-icon class="fa fa-arrow-left"></mat-icon>
-                            </button>
-                            <button mat-fab matStepperNext class="nextStepButton" color="primary" [disabled]="!appMailserver.isValidStep()">
-                                <mat-icon class="fa fa-arrow-right"></mat-icon>
-                            </button>
-                        </div>
-                    </mat-step>
                     <mat-step label="install">
                         <ng-template matStepLabel>Installation</ng-template>
                         <div class="stepContainer">
@@ -102,6 +88,20 @@
                             </button>
                         </div>
                     </mat-step>
+                    <mat-step [stepControl]="appMailserver.getFormGroup()" optional>
+                        <ng-template matStepLabel>{{lang.stepMailServer}}</ng-template>
+                        <div class="stepContainer">
+                            <div class="stepContent">
+                                <app-mailserver #appMailserver #stepContent (tiggerInstall)="endInstall()"></app-mailserver>
+                            </div>
+                            <button mat-fab matStepperPrevious class="previousStepButton" color="primary">
+                                <mat-icon class="fa fa-arrow-left"></mat-icon>
+                            </button>
+                            <button mat-fab class="nextStepButton" color="accent" [disabled]="!appMailserver.isValidStep()" (click)="endInstall()">
+                                <mat-icon class="fas fa-check-double"></mat-icon>
+                            </button>
+                        </div>
+                    </mat-step>
                     <ng-template matStepperIcon="edit">
                         <mat-icon class="fa fa-check stepIcon"></mat-icon>
                     </ng-template>
diff --git a/src/frontend/app/installer/installer.component.ts b/src/frontend/app/installer/installer.component.ts
index 0f84f239b69..9fb85d7aea3 100644
--- a/src/frontend/app/installer/installer.component.ts
+++ b/src/frontend/app/installer/installer.component.ts
@@ -17,6 +17,7 @@ import { finalize } from 'rxjs/internal/operators/finalize';
 import { catchError } from 'rxjs/internal/operators/catchError';
 import { of } from 'rxjs/internal/observable/of';
 import { FunctionsService } from '../../service/functions.service';
+import { InstallerService } from './installer.service';
 
 @Component({
     templateUrl: './installer.component.html',
@@ -42,7 +43,8 @@ export class InstallerComponent implements OnInit, AfterViewInit {
         public appService: AppService,
         private sortPipe: SortPipe,
         public dialog: MatDialog,
-        private functionService: FunctionsService
+        private functionService: FunctionsService,
+        private installerService: InstallerService
     ) { }
 
     ngOnInit(): void {
diff --git a/src/frontend/app/installer/installer.module.ts b/src/frontend/app/installer/installer.module.ts
index c27c72afb70..9fb2ab77161 100644
--- a/src/frontend/app/installer/installer.module.ts
+++ b/src/frontend/app/installer/installer.module.ts
@@ -10,6 +10,7 @@ import { CustomizationComponent } from './customization/customization.component'
 import { UseradminComponent } from './useradmin/useradmin.component';
 import { MailserverComponent } from './mailserver/mailserver.component';
 import { InstallerRoutingModule } from './installer-routing.module';
+import { InstallerService } from './installer.service';
 
 @NgModule({
     imports: [
@@ -27,6 +28,7 @@ import { InstallerRoutingModule } from './installer-routing.module';
         UseradminComponent,
         MailserverComponent
     ],
-    entryComponents: [InstallActionComponent]
+    entryComponents: [InstallActionComponent],
+    providers: [InstallerService]
 })
 export class InstallerModule { }
diff --git a/src/frontend/app/installer/installer.service.ts b/src/frontend/app/installer/installer.service.ts
new file mode 100644
index 00000000000..f6334595711
--- /dev/null
+++ b/src/frontend/app/installer/installer.service.ts
@@ -0,0 +1,18 @@
+import { Injectable } from '@angular/core';
+import { StepAction } from './types';
+
+@Injectable()
+export class InstallerService {
+
+    steps: StepAction[] = [];
+
+    constructor() { }
+
+    setStep(step: StepAction) {
+        this.steps.push(step);
+    }
+
+    isStepAlreadyLaunched(IdsStep: string) {
+        return this.steps.filter(step => IdsStep === step.idStep).length > 0;
+    }
+}
diff --git a/src/frontend/app/installer/mailserver/mailserver.component.ts b/src/frontend/app/installer/mailserver/mailserver.component.ts
index f4619a2eb34..47e97f03858 100644
--- a/src/frontend/app/installer/mailserver/mailserver.component.ts
+++ b/src/frontend/app/installer/mailserver/mailserver.component.ts
@@ -2,14 +2,12 @@ import { Component, OnInit, ViewChild } from '@angular/core';
 import { FormBuilder, FormGroup, Validators, ValidatorFn } from '@angular/forms';
 import { LANG } from '../../translate.component';
 import { StepAction } from '../types';
-import { DomSanitizer } from '@angular/platform-browser';
 import { NotificationService } from '../../../service/notification/notification.service';
-import { environment } from '../../../environments/environment';
-import { ScanPipe } from 'ngx-pipes';
-import { debounceTime, filter, tap, catchError } from 'rxjs/operators';
+import { tap, catchError } from 'rxjs/operators';
 import { HttpClient } from '@angular/common/http';
 import { of } from 'rxjs/internal/observable/of';
 import { MatDrawer } from '@angular/material/sidenav';
+import { InstallerService } from '../installer.service';
 
 declare var tinymce: any;
 
@@ -70,6 +68,7 @@ export class MailserverComponent implements OnInit {
         private _formBuilder: FormBuilder,
         private notify: NotificationService,
         public http: HttpClient,
+        private installerService: InstallerService
     ) {
         const valEmail: ValidatorFn[] = [Validators.pattern(/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/), Validators.required];
 
@@ -173,11 +172,13 @@ export class MailserverComponent implements OnInit {
     }
 
     initStep() {
-        return false;
+        if (this.installerService.isStepAlreadyLaunched('mailserver')) {
+            this.stepFormGroup.disable();
+        }
     }
 
     isValidStep() {
-        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid;
+        return this.stepFormGroup === undefined ? false : this.stepFormGroup.controls['firstCtrl'].valid;
     }
 
     getFormGroup() {
@@ -189,6 +190,23 @@ export class MailserverComponent implements OnInit {
     }
 
     getInfoToInstall(): StepAction[] {
+        this.notify.error('TO DO : WAIT BACK route : ../rest/installer/mailserver');
         return [];
+        /* return [{
+            idStep : 'mailserver',
+            body: {
+                smtp: this.stepFormGroup.controls['smtp'].value,
+                auth: this.stepFormGroup.controls['auth'].value,
+                user: this.stepFormGroup.controls['user'].value,
+                password: this.stepFormGroup.controls['password'].value,
+                secure: this.stepFormGroup.controls['secure'].value,
+                port: this.stepFormGroup.controls['port'].value,
+                charset: this.stepFormGroup.controls['charset'].value,
+                from: this.stepFormGroup.controls['from'].value
+            },
+            route: '../rest/installer/mailserver',
+            description: this.lang.stepMailServerActionDesc,
+            installPriority: 3
+        }];*/
     }
 }
diff --git a/src/frontend/app/installer/prerequisite/prerequisite.component.html b/src/frontend/app/installer/prerequisite/prerequisite.component.html
index bf5c3e80fd9..19252bd61e9 100644
--- a/src/frontend/app/installer/prerequisite/prerequisite.component.html
+++ b/src/frontend/app/installer/prerequisite/prerequisite.component.html
@@ -10,7 +10,7 @@
                 <mat-list-item>
                     <mat-icon mat-list-icon class="fa iconCheckPackage icon_{{package.state}}"></mat-icon>
                     <div mat-line class="packageName">
-                        {{lang['install_'+package.label]}} <i #toto="matTooltip" [id]="package.label" class="fa fa-info-circle" [matTooltip]="lang['install_'+package.label+'_desc']"
+                        {{lang['install_'+package.label]}} <i #packageItem="matTooltip" [id]="package.label" class="fa fa-info-circle" [matTooltip]="lang['install_'+package.label+'_desc']"
                         [matTooltipClass]="package.state !== 'ok' ? 'tooltip-red' : ''" matTooltipPosition="right"></i>
                     </div>
                 </mat-list-item>  
diff --git a/src/frontend/app/installer/prerequisite/prerequisite.component.ts b/src/frontend/app/installer/prerequisite/prerequisite.component.ts
index ab0a0b406ee..30b0f59df8b 100644
--- a/src/frontend/app/installer/prerequisite/prerequisite.component.ts
+++ b/src/frontend/app/installer/prerequisite/prerequisite.component.ts
@@ -107,7 +107,7 @@ export class PrerequisiteComponent implements OnInit {
 
     docMaarchUrl: string = `https://docs.maarch.org/gitbook/html/MaarchCourrier/${environment.VERSION.split('.')[0] + '.' + environment.VERSION.split('.')[1]}/guat/guat_prerequisites/home.html`;
 
-    @ViewChildren('toto') toto: QueryList<any>;
+    @ViewChildren('packageItem') packageItem: QueryList<any>;
 
     constructor(
         public http: HttpClient,
@@ -149,7 +149,7 @@ export class PrerequisiteComponent implements OnInit {
         Object.keys(this.packagesList).forEach(group => {
             this.packagesList[group].forEach((item: any, key: number) => {
                 if (this.packagesList[group][key].state === 'ko') {
-                    this.toto.toArray().filter((itemKo: any) => itemKo._elementRef.nativeElement.id === this.packagesList[group][key].label)[0].toggle();
+                    this.packageItem.toArray().filter((itemKo: any) => itemKo._elementRef.nativeElement.id === this.packagesList[group][key].label)[0].toggle();
                 }
                 i++;
             });
diff --git a/src/frontend/app/installer/types.ts b/src/frontend/app/installer/types.ts
index aa91a4d37b8..4bd495ea21e 100644
--- a/src/frontend/app/installer/types.ts
+++ b/src/frontend/app/installer/types.ts
@@ -7,6 +7,7 @@
  * @param installPriority action order (/!\ NEVER USE 1 IT IS FOR ROUTE /installer/initCustom)
  */
 export class StepAction {
+    idStep: string;
     route: string;
     body: any;
     description: any;
diff --git a/src/frontend/app/installer/useradmin/useradmin.component.html b/src/frontend/app/installer/useradmin/useradmin.component.html
index 1b5647d9aa3..eeb97488e02 100644
--- a/src/frontend/app/installer/useradmin/useradmin.component.html
+++ b/src/frontend/app/installer/useradmin/useradmin.component.html
@@ -28,10 +28,5 @@
             <mat-label>{{lang.email}}</mat-label>
             <input matInput formControlName="email">
         </mat-form-field>
-        <div style="text-align:center;">
-            <button mat-raised-button type="button" color="primary" (click)="launchInstall()" *ngIf="stepFormGroup.valid" style="font-size: 35px;padding: 20px;">
-                <i class=" far fa-hdd"></i> {{lang.launchInstall}}
-            </button>
-        </div>
     </form>
 </div>
diff --git a/src/frontend/app/installer/useradmin/useradmin.component.ts b/src/frontend/app/installer/useradmin/useradmin.component.ts
index d17d4de4558..43d2c42779a 100644
--- a/src/frontend/app/installer/useradmin/useradmin.component.ts
+++ b/src/frontend/app/installer/useradmin/useradmin.component.ts
@@ -1,8 +1,9 @@
 import { Component, OnInit, EventEmitter, Output } from '@angular/core';
-import { FormGroup, FormBuilder, Validators } from '@angular/forms';
+import { FormGroup, FormBuilder, Validators, ValidatorFn } from '@angular/forms';
 import { NotificationService } from '../../../service/notification/notification.service';
 import { LANG } from '../../translate.component';
 import { tap } from 'rxjs/internal/operators/tap';
+import { InstallerService } from '../installer.service';
 
 @Component({
     selector: 'app-useradmin',
@@ -20,9 +21,13 @@ export class UseradminComponent implements OnInit {
     constructor(
         private _formBuilder: FormBuilder,
         private notify: NotificationService,
+        private installerService: InstallerService
     ) {
+
+        const valLogin: ValidatorFn[] = [Validators.pattern(/^[\w.@-]*$/), Validators.required];
+
         this.stepFormGroup = this._formBuilder.group({
-            login: [{ value: 'superadmin' }, Validators.required],
+            login: ['superadmin', valLogin],
             password: ['', Validators.required],
             passwordConfirm: ['', Validators.required],
             email: ['dev@maarch.org', Validators.required],
@@ -57,11 +62,13 @@ export class UseradminComponent implements OnInit {
     }
 
     initStep() {
-        return false;
+        if (this.installerService.isStepAlreadyLaunched('userAdmin')) {
+            this.stepFormGroup.disable();
+        }
     }
 
     isValidStep() {
-        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid;
+        return this.stepFormGroup === undefined ? false : this.stepFormGroup.valid || this.installerService.isStepAlreadyLaunched('userAdmin');
     }
 
     getFormGroup() {
@@ -71,12 +78,15 @@ export class UseradminComponent implements OnInit {
     getInfoToInstall(): any[] {
         return [];
         /*return {
+            idStep : 'userAdmin',
             body : {
                 login: this.stepFormGroup.controls['login'].value,
                 password: this.stepFormGroup.controls['password'].value,
                 email: this.stepFormGroup.controls['email'].value,
             },
-            route : '/installer/useradmin'
+            route : '/installer/useradmin',
+            description: this.lang.stepUserAdminActionDesc,
+            installPriority: 3
         };*/
     }
 
diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts
index b6d375e41d8..b907939a1a2 100755
--- a/src/frontend/lang/lang-fr.ts
+++ b/src/frontend/lang/lang-fr.ts
@@ -1808,4 +1808,8 @@ export const LANG_FR = {
     "stepMailServer_desc": "Configurer votre serveur de mail afin de prévenir les utilisateurs par mail des différents échanges survenus dans l'application.<br/>Cette étape peut être passé et être configurée plus tard.",
     "stepMailServer_warning": "Si aucun serveur de mail n'est renseigné, les nouveaux utilisateurs ne recevront pas leur jeton de première connexion !",
     "checkCollaboraOnlineServer": "Communication avec le serveur Collabora Online",
+    "stepMailServerActionDesc": "Configuration du serveur de mail",    
+    "stepUserAdminActionDesc": "Création de l'utilisateur système",
+    "pathUnreacheable": "Chemin inaccessible",
+    "stepCustomizationActionDesc": "Mise en place du paramétrage personnalisé",
 };
-- 
GitLab