You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dlab.apache.org by dg...@apache.org on 2020/07/24 13:04:22 UTC

[incubator-dlab] 01/02: [DLAB-1960]: Fixed library issues

This is an automated email from the ASF dual-hosted git repository.

dgnatyshyn pushed a commit to branch DLAB-1960
in repository https://gitbox.apache.org/repos/asf/incubator-dlab.git

commit d32fc3cd60238748fd539daf2994d177b9f30203
Author: Dmytro_Gnatyshyn <di...@ukr.net>
AuthorDate: Fri Jul 24 15:39:48 2020 +0300

    [DLAB-1960]: Fixed library issues
---
 .../install-libraries.component.html               |  59 +++++----
 .../install-libraries.component.scss               |  10 +-
 .../install-libraries.component.ts                 | 137 ++++++++++++---------
 .../install-libraries/install-libraries.model.ts   |   2 +-
 .../resources/webapp/src/assets/styles/_theme.scss |   8 ++
 .../src/main/resources/webapp/src/styles.scss      |   3 +-
 6 files changed, 130 insertions(+), 89 deletions(-)

diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.html
index c05b6f9..54f1d86 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.html
@@ -28,13 +28,14 @@
       <p class=" message">Cannot install libraries: Exploratory
         <strong>{{ notebook?.name }}</strong> is not running</p>
     </div>
-    <div class="loading-block" *ngIf="!libs_uploaded && uploading && data.status === 'running'">
+    <div *ngIf="notebook?.status === 'running'" class="top-wrapper">
+      <div class="loading-block" *ngIf="!libs_uploaded && uploading && data.status === 'running'">
       <div class="uploading">
         <p>Please wait until DLab loads full list of available libraries for you...</p>
         <img src="assets/img/gif-spinner.gif" alt="loading">
       </div>
     </div>
-    <div *ngIf="notebook?.status === 'running' && !uploading" class="lib-view-wrap">
+      <div *ngIf="notebook?.status === 'running' && !uploading" class="lib-view-wrap">
       <div class="search-box">
         <div class="search-form">
           <div>
@@ -48,7 +49,7 @@
               <label class="label">Select group</label>
               <div class="control">
                 <dropdown-list #groupSelect (selectedItem)="onUpdate($event)" (emitClick)="emitClick()"></dropdown-list>
-                <span class="error-message" *ngIf="!group && libSearch.value">Group field is required. Please choose appropriate group.</span>
+                <span class="error-message" *ngIf="!group && libSearch?.value">Group field is required. Please choose appropriate group.</span>
               </div>
             </div>
           </div>
@@ -59,7 +60,6 @@
                 <span class="other-message" *ngIf="group === 'others'">Other group can include libs from Python 2 and Python 3 groups</span>
                 <input
                   type="text" [placeholder]="'Enter library name'"
-                  [(ngModel)]="lib.name"
                   [disabled]="!group"
                   [matAutocomplete]="auto"
                   [formControl]="libSearch"
@@ -68,15 +68,13 @@
                 >
               </div>
               <mat-autocomplete #auto="matAutocomplete" class="suggestions" >
-                <ng-template ngFor let-item [ngForOf]="filteredList" let-i="index" *ngIf="!selectedLib">
-                  <mat-option >
+                <ng-template ngFor let-item [ngForOf]="filteredList" let-i="index">
+                  <mat-option>
                     <div class="option" (click)="selectLibrary(item);$event.stopPropagation()" [ngClass]="{'not-allow': item.isInSelectedList}">
                       <a *ngIf="!item.isInSelectedList">
-                        <span [innerHTML]="item.name | highlight:query"></span>
+                        <span [innerHTML]="item.name | highlight:lib.name.toLowerCase()"></span>
                       </a>
-                      <span *ngIf="item.isInSelectedList">{{ item.name }}
-<!--                        <span *ngIf="item.version && item.version !== 'N/A' && item.isInstalled && !item.isInSelectedList">{{ item.version }}</span>-->
-                      </span>
+                      <span *ngIf="item.isInSelectedList">{{ item.name }}</span>
                       <strong *ngIf="item.isInSelectedList">selected
                         <i class="material-icons">done</i>
                       </strong>
@@ -113,10 +111,10 @@
               <div class="control-group constol-select java-library-search">
                 <label class="label">Library</label>
                 <div class="control control-relative">
-                 <span class="other-message" *ngIf="lib.name.length && !this.selectedLib">groupId:artifactId:versionId</span>
+                 <span class="other-message" *ngIf="lib.name?.length && !this.selectedLib">groupId:artifactId:versionId</span>
                  <input
                     type="text" [placeholder]="'Enter library name in <groupId>:<artifactId>:<versionId> format'"
-                    [(ngModel)]="lib.name"
+
                     [disabled]="!group"
                     [matAutocomplete]="auto"
                     [formControl]="libSearch"
@@ -132,11 +130,11 @@
                 </span>
                 </div>
                 <mat-autocomplete #auto="matAutocomplete" class="suggestions">
-                  <ng-template ngFor let-item [ngForOf]="filteredList" let-i="index" *ngIf="!selectedLib">
+                  <ng-template ngFor let-item [ngForOf]="filteredList" let-i="index">
                     <mat-option >
                       <div class="option" (click)="selectLibrary(item);$event.stopPropagation()" [ngClass]="{'not-allow': item.isInSelectedList}">
                         <a *ngIf="!item.isInSelectedList">
-                          <span [innerHTML]="item.name + ':' + item.version | highlight:query">
+                          <span [innerHTML]="item.name + ':' + item.version | highlight:item.name.toLowerCase()">
                              <span>{{ item.version }}</span>
                           </span>
                         </a>
@@ -231,6 +229,10 @@
         </div>
       </div>
     </div>
+    </div>
+
+
+
     <div class="libs-info">
       <mat-list>
         <mat-list-item class="list-header">
@@ -344,8 +346,8 @@
                 <div class="wrap-col">
                   <div class="lib-destination-col">{{ item.resource }}</div>
                   <div class="lib-resource-type-col">{{ item.resourceType }}</div>
-                  <div class="lib-status-col" ngClass="{{ item.status.toLowerCase() || 'failed' }}">{{ item.status.replace('_', ' ') }}
-                    <div class="warn-action" *ngIf="(item.status === 'failed' || item.status.toLowerCase() === 'invalid_version') && notebook?.status === 'running'">
+                  <div class="lib-status-col" ngClass="{{ item.status.toLowerCase() || 'installation_error' }}">{{ item.status.replace('_', ' ') }}
+                    <div class="warn-action" *ngIf="(item.status === 'installation_error' || item.status.toLowerCase() === 'invalid_version') && notebook?.status === 'running'">
                       <div *ngIf="!item.available_versions">
                         <span *ngIf="!installingInProgress" (click)="reinstallLibrary(item, lib)" matTooltip="Retry installation" matTooltipPosition="above">
                           <i class="material-icons">replay</i>
@@ -355,22 +357,23 @@
                           <i class="material-icons">replay</i>
                         </span>
                       </div>
-                      <div *ngIf="item.status === 'failed' && item.error" class="lib-error" (click)="showErrorMessage(item)">
-                        <i class="material-icons">error_outline</i>
+                      <div *ngIf="item.status === 'installation_error' && item.error" class="lib-error" (click)="showErrorMessage(item)" matTooltip="Show error message" matTooltipPosition="above">
+                        <i class="material-icons terminated">error_outline</i>
                       </div>
                       <div class="lib-error"
-                           *ngIf="item.status.toLowerCase() === 'invalid_version' && item.available_versions?.length" class="lib-error"
+                           *ngIf="item.status.toLowerCase() === 'invalid_version' && item.available_versions?.length"
                            (click)="openLibInfo(item, 'available');$event.stopPropagation()"
                            matTooltip="Show available version" matTooltipPosition="above"
                       >
-                        <i class="material-icons terminated">error_outline</i>
+                        <i class="material-icons">error_outline</i>
                       </div>
-                    </div>
-                    <div class="warn-action" matTooltip="Show installed dependency" matTooltipPosition="above" *ngIf="item.add_pkgs?.length">
+                      <div matTooltip="Show installed dependency" matTooltipPosition="above" *ngIf="item.add_pkgs?.length">
                         <span class="info-icon" (click)="openLibInfo(item, 'added');$event.stopPropagation() ">
                           <i class="material-icons">info</i>
                         </span>
+                      </div>
                     </div>
+
                   </div>
                 </div>
 
@@ -411,8 +414,16 @@
       </mat-list>
     </div>
     <div class="m-top-15 actions" *ngIf="!uploading && notebook?.status === 'running'">
-      <button mat-raised-button type="button" class="butt action" (click)="dialogRef.close()">Close</button>
-      <button mat-raised-button type="submit" class="butt butt-success action" (click)="model.confirmAction()" [disabled]="!model.selectedLibs.length || installingInProgress || !destination">Install</button>
+      <button mat-raised-button type="button" class="butt action close-btn" (click)="dialogRef.close()">Close</button>
+      <span matTooltip="Please wait until lib installation completes" [matTooltipDisabled]="!installingInProgress" matTooltipPosition="above">
+        <button mat-raised-button type="submit"
+                class="butt butt-success action install-btn"
+                (click)="model.confirmAction()"
+                [disabled]="!model.selectedLibs.length || installingInProgress || !destination"
+        >
+          Install
+        </button>
+      </span>
     </div>
    </div>
   </div>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.scss
index 86cd434..48ed412 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.scss
@@ -65,6 +65,10 @@
   height: 30%;
 }
 
+.top-wrapper{
+  height: 285px;
+}
+
 .install-libraries {
   height: 100%;
   padding-bottom: 130px;
@@ -78,7 +82,6 @@
   }
 
   .info {
-    height: 40%;
     display: flex;
     flex-direction: row;
     align-items: center;
@@ -99,7 +102,6 @@
   .lib-view-wrap {
     display: flex;
     flex-direction: column;
-    height: 40%;
   }
 
   .error {
@@ -342,7 +344,6 @@ mat-chip.mat-chip:not(.mat-basic-chip) {
   display: flex;
   flex: 1 1 auto;
   min-height: 0px;
-  height: 50%;
 
   .mat-list {
     width: 100%;
@@ -555,6 +556,9 @@ mat-chip.mat-chip:not(.mat-basic-chip) {
 
 .btn{
   line-height: 26px;
+  &.install-btn{
+    margin-left: 0;
+  }
 }
 
 .control-relative{
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.ts
index 169156d..ae38165 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.component.ts
@@ -22,12 +22,13 @@ import {Component, OnInit, ViewChild, ViewEncapsulation, ChangeDetectorRef, Inje
 import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { FormControl } from '@angular/forms';
 import { ToastrService } from 'ngx-toastr';
-import { debounceTime } from 'rxjs/operators';
+import {debounceTime, takeUntil} from 'rxjs/operators';
 
 import { InstallLibrariesModel } from './install-libraries.model';
 import { LibrariesInstallationService } from '../../../core/services';
 import { SortUtils, HTTP_STATUS_CODES } from '../../../core/util';
 import {FilterLibsModel} from './filter-libs.model';
+import {Subject} from 'rxjs';
 
 interface Library {
   name: string;
@@ -42,16 +43,13 @@ interface Library {
   encapsulation: ViewEncapsulation.None
 })
 export class InstallLibrariesComponent implements OnInit, OnDestroy {
-
+  private unsubscribe$ = new Subject();
   public model: InstallLibrariesModel;
   public notebook: any;
   public filteredList: any = [];
   public groupsList: Array<string>;
   public notebookLibs: Array<any> = [];
-  public notebookFailedLibs: Array<any> = [];
   public loadLibsTimer: any;
-
-  public query: string = '';
   public group: string;
   public destination: any;
   public uploading: boolean = false;
@@ -81,10 +79,8 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
   @ViewChild('groupSelect', { static: false }) group_select;
   @ViewChild('resourceSelect', { static: false }) resource_select;
   @ViewChild('trigger', { static: false }) matAutoComplete;
-  public isLibInfoOpened = {  };
   private isLibExist: boolean;
   public lib: Library = {name: '', version: ''};
-  public javaLib: {group, artifact, version} = {group: '', artifact: '', version: ''};
   private selectedLib: any = null;
 
   constructor(
@@ -103,9 +99,11 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
     this.libSearch.disable();
     this.libSearch.valueChanges
       .pipe(
-      debounceTime(1000))
-      .subscribe(newValue => {
-        this.query = newValue;
+      debounceTime(1000),
+      takeUntil(this.unsubscribe$)
+      )
+      .subscribe(value => {
+        this.lib.name = value;
         this.isDuplicated(this.lib);
         this.filterList();
       });
@@ -114,12 +112,17 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
   ngOnDestroy() {
     window.clearTimeout(this.loadLibsTimer);
     window.clearTimeout(this.clear);
+    this.unsubscribe$.next();
+    this.unsubscribe$.complete();
   }
 
   uploadLibGroups(): void {
     this.libs_uploaded = false;
     this.uploading = true;
     this.librariesInstallationService.getGroupsList(this.notebook.project, this.notebook.name, this.model.computational_name)
+      .pipe(
+        takeUntil(this.unsubscribe$)
+      )
       .subscribe(
         response => {
           this.libsUploadingStatus(response);
@@ -150,7 +153,7 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
 
   public filterList(): void {
     this.validity_format = '';
-    (this.query && this.query.length >= 2 && this.group && !this.selectedLib) ? this.getFilteredList() : this.filteredList = null;
+    (this.lib.name && this.lib.name.length >= 2 && this.group ) ? this.getFilteredList() : this.filteredList = null;
   }
 
   public filterGroups(groupsList) {
@@ -166,6 +169,7 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
     if ($event.model.type === 'group_lib') {
       this.libSearch.enable();
       this.group = $event.model.value;
+      this.lib = {name: '', version: ''};
     } else if ($event.model.type === 'destination') {
       this.resetDialog();
       this.destination = $event.model.value;
@@ -186,25 +190,27 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
   public isDuplicated(item) {
     if (this.filteredList) {
       if (this.group !== 'java') {
-        this.selectedLib = this.filteredList.find(lib => lib.name === item.name);
+        this.selectedLib = this.filteredList.find(lib => lib.name.toLowerCase() === item.name.toLowerCase());
       } else {
         this.selectedLib = this.filteredList.find(lib => {
-          return lib.name === item.name.substring(0, item.name.lastIndexOf(':'));
+          return lib.name.toLowerCase() === item.name.substring(0, item.name.lastIndexOf(':')).toLowerCase();
         });
       }
     } else {
       this.selectedLib = null;
     }
-
-    if (this.selectedLib) {
-      this.filteredList = null;
-    }
   }
 
   public addLibrary(item): void {
     if (this.selectedLib && !this.selectedLib.isInSelectedList) {
-      this.model.selectedLibs.push({ group: this.group, name: item.name, version: item.version || 'N/A' });
-      this.query = '';
+      if (this.group !== 'java') {
+        this.model.selectedLibs.push({ group: this.group, name: item.name.toLowerCase(), version: item.version.toLowerCase() || 'N/A' });
+      } else {
+        this.model.selectedLibs.push({
+          group: this.group, name: item.name.substring(0, item.name.lastIndexOf(':')).toLowerCase(),
+          version: item.name.substring(item.name.lastIndexOf(':') + 1).toLowerCase() || 'N/A'
+        });
+      }
       this.libSearch.setValue('');
       this.lib = {name: '', version: ''};
       this.filteredList = null;
@@ -240,7 +246,7 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
           this.resetDialog();
         }
       },
-      error => this.toastr.error(error.message || 'Library installation failed!', 'Oops!'),
+      error => this.toastr.error(error.message || 'Library installation error!', 'Oops!'),
       () => {
         this.getInstalledLibrariesList(true);
         this.changeDetector.detectChanges();
@@ -274,6 +280,9 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
 
   private getInstalledLibrariesList(init?: boolean) {
     this.model.getInstalledLibrariesList(this.notebook)
+      .pipe(
+        takeUntil(this.unsubscribe$)
+      )
       .subscribe((data: any) => {
         if ( !this.filtredNotebookLibs.length || data.length !== this.notebookLibs.length) {
           this.filtredNotebookLibs = [...data];
@@ -305,6 +314,9 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
 
   private getInstalledLibsByResource() {
     this.librariesInstallationService.getInstalledLibsByResource(this.notebook.project, this.notebook.name, this.model.computational_name)
+      .pipe(
+        takeUntil(this.unsubscribe$)
+      )
       .subscribe((data: any) => this.destination.libs = data);
   }
 
@@ -322,52 +334,57 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
 
   private getFilteredList(): void {
     this.validity_format = '';
-
-    if (this.group === 'java') {
-      this.model.getDependencies(this.query)
-        .subscribe(
-          libs => {
-            this.filteredList = [libs];
-            this.filteredList.forEach(lib => {
-              lib.isInSelectedList = this.model.selectedLibs.some(el => lib.name === el.name.substring(0, el.name.lastIndexOf(':')));
-              lib.isInstalled = this.notebookLibs.some(libr => {
-              // && lib.version === item.name.substring(item.name.lastIndexOf(':') + 1
-                return  lib.name === libr.name.substring(0, libr.name.lastIndexOf(':')) &&
-                this.group === libr.group &&
-                libr.status.some(res => res.resource === this.destination.name);
+    if (this.lib.name.length > 1) {
+      if (this.group === 'java') {
+        this.model.getDependencies(this.lib.name)
+          .pipe(
+            takeUntil(this.unsubscribe$)
+          )
+          .subscribe(
+            libs => {
+              this.filteredList = [libs];
+              this.filteredList.forEach(lib => {
+                lib.isInSelectedList = this.model.selectedLibs.some(el => lib.name.toLowerCase() === el.name.substring(0, el.name.lastIndexOf(':')).toLowerCase());
+                lib.isInstalled = this.notebookLibs.some(libr => {
+                    return lib.name.toLowerCase() === libr.name.substring(0, libr.name.lastIndexOf(':')).toLowerCase() &&
+                      this.group === libr.group &&
+                      libr.status.some(res => res.resource === this.destination.name);
+                  }
+                );
+              });
+              this.isDuplicated(this.lib);
+            },
+            error => {
+              if (error.status === HTTP_STATUS_CODES.NOT_FOUND
+                || error.status === HTTP_STATUS_CODES.BAD_REQUEST
+                || error.status === HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR) {
+                this.validity_format = error.message;
+                this.filteredList = null;
               }
-             );
             });
-            this.isDuplicated(this.lib);
-          },
-          error => {
-            if (error.status === HTTP_STATUS_CODES.NOT_FOUND
-              || error.status === HTTP_STATUS_CODES.BAD_REQUEST
-              || error.status === HTTP_STATUS_CODES.INTERNAL_SERVER_ERROR) {
-              this.validity_format = error.message;
-              this.filteredList = null;
-            }
-          });
-    } else {
-       this.getMatchedLibs();
+      } else {
+        this.getMatchedLibs();
+      }
     }
   }
 
   private getMatchedLibs() {
-    if (this.query.length > 1) {
-      this.model.getLibrariesList(this.group, this.query)
-        .subscribe((libs: Library[]) => {
-          this.isLibExist = libs.some(v => v.name === this.query);
-          this.filteredList = libs;
-          this.filteredList.forEach(lib => {
-            lib.isInSelectedList = this.model.selectedLibs.some(el => el.name === lib.name);
-            lib.isInstalled = this.notebookLibs.some(libr => lib.name === libr.name &&
-              this.group === libr.group &&
-              libr.status.some(res => res.resource === this.destination.name));
-          });
-          this.isDuplicated(this.lib);
+    this.model.getLibrariesList(this.group, this.lib.name.toLowerCase())
+      .pipe(
+        takeUntil(this.unsubscribe$)
+      )
+      .subscribe((libs: Library[]) => {
+        this.isLibExist = libs.some(v => v.name.toLowerCase() === this.lib.name.toLowerCase());
+        this.filteredList = libs;
+        this.filteredList.forEach(lib => {
+          lib.isInSelectedList = this.model.selectedLibs.some(el => el.name.toLowerCase() === lib.name.toLowerCase());
+          lib.isInstalled = this.notebookLibs.some(libr => lib.name === libr.name &&
+            this.group === libr.group &&
+            libr.status.some(res => res.resource === this.destination.name));
         });
-    }
+        this.isDuplicated(this.lib);
+      });
+
   }
 
   private selectorsReset(): void {
@@ -378,7 +395,7 @@ export class InstallLibrariesComponent implements OnInit, OnDestroy {
 
   private resetDialog(): void {
     this.group = '';
-    this.query = '';
+    this.lib.name = '';
     this.libSearch.setValue('');
     this.isInstalled = false;
     this.isInSelectedList = false;
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.model.ts
index 65896d3..b201904 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.model.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/install-libraries/install-libraries.model.ts
@@ -60,7 +60,7 @@ export class InstallLibrariesModel {
       project_name: this.notebook.project,
       exploratory_name: this.notebook.name,
       group: group,
-      start_with: query.slice(0, query.indexOf(':'))
+      start_with: query
     };
     if (this.computational_name)
       lib_query.computational_name = this.computational_name;
diff --git a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
index 2500caf..92a7349 100644
--- a/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
+++ b/services/self-service/src/main/resources/webapp/src/assets/styles/_theme.scss
@@ -49,6 +49,14 @@
 
     &.action {
       width: 140px;
+
+      &.install-btn{
+        margin-left: 0;
+      }
+
+      &.close-btn{
+        margin-right: 25px;
+      }
     }
 
     &.butt-success {
diff --git a/services/self-service/src/main/resources/webapp/src/styles.scss b/services/self-service/src/main/resources/webapp/src/styles.scss
index 459f495..006bd60 100644
--- a/services/self-service/src/main/resources/webapp/src/styles.scss
+++ b/services/self-service/src/main/resources/webapp/src/styles.scss
@@ -141,7 +141,8 @@ mat-chip.mat-chip strong {
 .failed,
 .deleting,
 .deleted,
-.invalid_version{
+.invalid_version,
+.installation_error{
   color: #f1696e;
 }
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@dlab.apache.org
For additional commands, e-mail: commits-help@dlab.apache.org