You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@dlab.apache.org by an...@apache.org on 2019/06/19 12:56:16 UTC

[incubator-dlab] 03/03: [DLAB-805]: clusters creation validation fixes

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

ankovalyshyn pushed a commit to branch feature/projects
in repository https://gitbox.apache.org/repos/asf/incubator-dlab.git

commit a825837a397303dc7ae0e69f58ba605b6dbdc27f
Author: Andriana Kovalyshyn <An...@epam.com>
AuthorDate: Wed Jun 19 15:55:58 2019 +0300

    [DLAB-805]: clusters creation validation fixes
---
 ...utational-resource-create-dialog.component.html |  35 +---
 ...mputational-resource-create-dialog.component.ts | 190 +++++++--------------
 .../resources-grid/resources-grid.component.html   |  88 +++++-----
 3 files changed, 112 insertions(+), 201 deletions(-)

diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.html
index f63602d..627b7de 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.html
@@ -24,34 +24,20 @@
   </header>
   <div class="dialog-content selection">
     <div class="content-box mat-reset">
-      <form [formGroup]="resourceForm" *ngIf="cluster_types.length && resourceForm; else empty">
+      <form [formGroup]="resourceForm" *ngIf="clusterTypes.length && resourceForm; else empty">
 
         <div class="form-wrapper" [ngClass]="{ compress: selectedImage?.image === 'docker.dlab-dataengine' }">
           <div class="col">
 
-            <!-- <div class="control-group" *ngIf="PROVIDER !== 'azure'" [hidden]="model.resourceImages.length === 1">
-              <label class="label">Select cluster type</label>
-              <div class="control">
-                <dropdown-list #clusterType (selectedItem)="onUpdate($event)"></dropdown-list>
-              </div>
-            </div> 
-
-            <div class="control-group" *ngIf="PROVIDER !== 'azure'" [hidden]="!selectedImage.templates.length">
-              <label class="label">Select template</label>
-              <div class="control">
-                <dropdown-list #templatesList (selectedItem)="onUpdate($event)"></dropdown-list>
-              </div>
-            </div> -->
-
-            <div class="control-group" *ngIf="PROVIDER !== 'azure'" [hidden]="cluster_types.length === 1">
+            <div class="control-group" *ngIf="PROVIDER !== 'azure'" [hidden]="clusterTypes.length === 1">
               <label class="label">Select cluster type</label>
               <div class="control selector-wrapper">
                 <mat-form-field>
                   <mat-label>Select cluster type</mat-label>
                   <mat-select formControlName="template_name" disableOptionCentering>
-                    <mat-option *ngFor="let type of cluster_types" [value]="type.template_name"
+                    <mat-option *ngFor="let type of clusterTypes" [value]="type.template_name"
                       (click)="selectImage(type)">{{ type.template_name }}</mat-option>
-                    <mat-option *ngIf="!cluster_types.length" class="multiple-select ml-10" disabled>Clusters types list
+                    <mat-option *ngIf="!clusterTypes.length" class="multiple-select ml-10" disabled>Clusters types list
                       is empty</mat-option>
                   </mat-select>
                   <button class="caret">
@@ -112,19 +98,6 @@
                 </span>
               </div>
             </div>
-            <!-- <div class="control-group" *ngIf="selectedImage?.image">
-              <label class="label" *ngIf="selectedImage">{{ DICTIONARY[selectedImage.image].data_engine_master_instance_size }}</label>
-              <div class="control">
-                <dropdown-list #masterShapesList (selectedItem)="onUpdate($event)"></dropdown-list>
-              </div>
-            </div> -->
-
-            <!-- <div class="control-group" *ngIf="selectedImage?.image" [hidden]="selectedImage?.image === 'docker.dlab-dataengine'">
-              <label class="label">{{ DICTIONARY[selectedImage.image].data_engine_slave_instance_size }}</label>
-              <div class="control">
-                <dropdown-list #shapesSlaveList (selectedItem)="onUpdate($event)"></dropdown-list>
-              </div>
-            </div> -->
 
             <div class="control-group" *ngIf="selectedImage?.image">
               <label class="label">{{ DICTIONARY[selectedImage.image].data_engine_master_instance_size }}</label>
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.ts
index b7b77fe..85ade39 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/computational/computational-resource-create-dialog/computational-resource-create-dialog.component.ts
@@ -17,14 +17,14 @@
  * under the License.
  */
 
-import { Component, OnInit, EventEmitter, Output, ViewChild, Inject } from '@angular/core';
+import { Component, OnInit, ViewChild, Inject } from '@angular/core';
 import { FormGroup, FormBuilder, Validators } from '@angular/forms';
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
 import { ToastrService } from 'ngx-toastr';
 
 import { ComputationalResourceModel } from './computational-resource-create.model';
 import { UserResourceService } from '../../../core/services';
-import { HTTP_STATUS_CODES, CheckUtils } from '../../../core/util';
+import { HTTP_STATUS_CODES, PATTERNS, CheckUtils } from '../../../core/util';
 
 import { DICTIONARY } from '../../../../dictionary/global.dictionary';
 import { CLUSTER_CONFIGURATION } from './cluster-configuration-templates';
@@ -42,17 +42,10 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
   readonly CheckUtils = CheckUtils;
 
   notebook_instance: any;
-  full_list: any;
-
-  cluster_types = [];
+  resourcesList: any;
+  clusterTypes = [];
   selectedImage: any;
-
-  shapes: any;
   spotInstance: boolean = true;
-  clusterNamePattern: string = '[-_a-zA-Z0-9]*[_-]*[a-zA-Z0-9]+';
-  nodeCountPattern: string = '^[1-9]\\d*$';
-  delimitersRegex = /[-_]?/g;
-  integerRegex = '^[0-9]*$';
 
   public minInstanceNumber: number;
   public maxInstanceNumber: number;
@@ -62,85 +55,29 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
   public maxSpotPrice: number = 0;
   public resourceForm: FormGroup;
 
-  // @ViewChild('name') name;
-  // @ViewChild('templatesList') templates_list;
-  // @ViewChild('masterShapesList') master_shapes_list;
-  // @ViewChild('shapesSlaveList') slave_shapes_list;
   @ViewChild('spotInstancesCheck') spotInstancesSelect;
   @ViewChild('preemptibleNode') preemptible;
   @ViewChild('configurationNode') configuration;
 
-  @Output() buildGrid: EventEmitter<{}> = new EventEmitter();
-
   constructor(
     @Inject(MAT_DIALOG_DATA) public data: any,
     public toastr: ToastrService,
+    public dialogRef: MatDialogRef<ComputationalResourceCreateDialogComponent>,
     private userResourceService: UserResourceService,
     private model: ComputationalResourceModel,
-    private _fb: FormBuilder,
-    public dialogRef: MatDialogRef<ComputationalResourceCreateDialogComponent>,
+    private _fb: FormBuilder
   ) { }
 
   ngOnInit() {
     this.initFormModel();
     this.notebook_instance = this.data.notebook;
-    this.full_list = this.data.full_list;
+    this.resourcesList = this.data.resourcesList;
     this.getTemplates(this.notebook_instance.project);
   }
 
-  // public onUpdate($event): void {
-  //   if ($event.model.type === 'template') {
-  //     this.model.setSelectedTemplate($event.model.index);
-  //     this.master_shapes_list.setDefaultOptions(this.model.selectedImage.shapes.resourcesShapeTypes,
-  //       this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'description'), 'master_shape', 'description', 'json');
-  //     this.slave_shapes_list.setDefaultOptions(this.model.selectedImage.shapes.resourcesShapeTypes,
-  //       this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'description'), 'slave_shape', 'description', 'json');
-
-  //     this.shapes.master_shape = this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'type');
-  //     this.shapes.slave_shape = this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'type');
-  //   }
-  //   if ($event.model.type === 'cluster_type') {
-  //     this.model.setSelectedClusterType($event.model.index);
-  //     this.setDefaultParams();
-  //     this.getComputationalResourceLimits();
-  //     this.selectConfiguration();
-  //   }
-
-  //   if (this.shapes[$event.model.type])
-  //     this.shapes[$event.model.type] = $event.model.value.type;
-
-  //   if (DICTIONARY.cloud_provider === 'aws')
-  //     if ($event.model.type === 'slave_shape' && this.spotInstancesSelect.nativeElement['checked']) {
-  //       this.spotInstance = $event.model.value.spot;
-  //     }
-  // }
-
-  public createComputationalResource(data) {
-    this.model.createComputationalResource(data, this.selectedImage, this.notebook_instance, this.spotInstance)
-      .subscribe((response: any) => {
-        if (response.status === HTTP_STATUS_CODES.OK) {
-          this.dialogRef.close();
-          this.buildGrid.emit();
-        }
-      });
-  }
-
-  public containsComputationalResource(conputational_resource_name: string): boolean {
-    if (conputational_resource_name)
-      for (let index = 0; index < this.full_list.length; index++) {
-        if (this.notebook_instance.name === this.full_list[index].name) {
-          for (let iindex = 0; iindex < this.full_list[index].resources.length; iindex++) {
-            const computational_name = this.full_list[index].resources[iindex].computational_name.toString().toLowerCase();
-            if (this.delimitersFiltering(conputational_resource_name) === this.delimitersFiltering(computational_name))
-              return true;
-          }
-        }
-      }
-    return false;
-  }
-
-  public delimitersFiltering(resource): string {
-    return resource.replace(this.delimitersRegex, '').toString().toLowerCase();
+  public selectImage($event) {
+    this.selectedImage = $event;
+    this.getComputationalResourceLimits();
   }
 
   public selectSpotInstances($event?): void {
@@ -154,15 +91,6 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
     }
   }
 
-  public selectTemplate($event) {
-    if (DICTIONARY.cloud_provider === 'aws') this.spotInstancesSelect.nativeElement['checked'];
-  }
-
-  public selectImage($event) {
-    this.selectedImage = $event;
-    this.getComputationalResourceLimits();
-  }
-
   public selectPreemptibleNodes($event) {
     if ($event.target.checked)
       this.resourceForm.controls['preemptible_instance_number'].setValue(this.minPreemptibleInstanceNumber);
@@ -179,15 +107,12 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
     }
   }
 
-  private filterAvailableSpots() {
-    const filtered = JSON.parse(JSON.stringify(this.selectedImage.computation_resources_shapes));
-    for (const item in this.selectedImage.computation_resources_shapes) {
-      filtered[item] = filtered[item].filter(el => el.spot);
-      if (filtered[item].length <= 0) {
-        delete filtered[item];
-      }
-    }
-    return filtered;
+  public preemptibleCounter($event, action): void {
+    $event.preventDefault();
+
+    const value = this.resourceForm.controls['preemptible_instance_number'].value;
+    const newValue = (action === 'increment' ? Number(value) + 1 : Number(value) - 1);
+    this.resourceForm.controls.preemptible_instance_number.setValue(newValue);
   }
 
   public isAvailableSpots(): boolean {
@@ -197,12 +122,11 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
     return false;
   }
 
-  public preemptibleCounter($event, action): void {
-    $event.preventDefault();
-
-    const value = this.resourceForm.controls['preemptible_instance_number'].value;
-    const newValue = (action === 'increment' ? Number(value) + 1 : Number(value) - 1);
-    this.resourceForm.controls.preemptible_instance_number.setValue(newValue);
+  public createComputationalResource(data) {
+    this.model.createComputationalResource(data, this.selectedImage, this.notebook_instance, this.spotInstance)
+      .subscribe((response: any) => {
+        if (response.status === HTTP_STATUS_CODES.OK) this.dialogRef.close();
+      });
   }
 
   private initFormModel(): void {
@@ -211,10 +135,10 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
       version: [''],
       shape_master: ['', Validators.required],
       shape_slave: [''],
-      cluster_alias_name: ['', [Validators.required, Validators.pattern(this.clusterNamePattern),
+      cluster_alias_name: ['', [Validators.required, Validators.pattern(PATTERNS.namePattern),
       this.providerMaxLength, this.checkDuplication.bind(this)]],
-      instance_number: ['', [Validators.required, Validators.pattern(this.nodeCountPattern), this.validInstanceNumberRange.bind(this)]],
-      preemptible_instance_number: [0, Validators.compose([Validators.pattern(this.integerRegex), this.validPreemptibleRange.bind(this)])],
+      instance_number: ['', [Validators.required, Validators.pattern(PATTERNS.nodeCountPattern), this.validInstanceNumberRange.bind(this)]],
+      preemptible_instance_number: [0, Validators.compose([Validators.pattern(PATTERNS.integerRegex), this.validPreemptibleRange.bind(this)])],
       instance_price: [0, [this.validInstanceSpotRange.bind(this)]],
       configuration_parameters: ['', [this.validConfiguration.bind(this)]]
     });
@@ -249,6 +173,7 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
     }
   }
 
+  //  Validation
   private validInstanceNumberRange(control) {
     if (control && control.value)
       if (DICTIONARY.cloud_provider === 'gcp' && this.selectedImage.image === 'docker.dlab-dataengine-service') {
@@ -304,25 +229,15 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
       return control.value.length <= 10 ? null : { valid: false };
   }
 
-  private setDefaultParams(): void {
-    // if (this.model.selectedImage && this.model.selectedImage.shapes) {
-    //   this.filterShapes();
-    //   this.shapes = {
-    //     master_shape: this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'type'),
-    //     slave_shape: this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'type')
-    //   };
-    //   if (DICTIONARY.cloud_provider !== 'azure' && this.cluster_type) {
-    //     this.cluster_type.setDefaultOptions(this.model.resourceImages,
-    //       this.model.selectedImage.template_name, 'cluster_type', 'template_name', 'array');
-    //       // if (this.model.selectedImage.image === 'docker.dlab-dataengine-service')
-    //       //   this.templates_list.setDefaultOptions(this.model.templates,
-    //       //     this.model.selectedItem.version, 'template', 'version', 'array');
-    //   }
-    //   this.master_shapes_list && this.master_shapes_list.setDefaultOptions(this.model.selectedImage.shapes.resourcesShapeTypes,
-    //     this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'description'), 'master_shape', 'description', 'json');
-    //     this.slave_shapes_list && this.slave_shapes_list.setDefaultOptions(this.model.selectedImage.shapes.resourcesShapeTypes,
-    //     this.shapePlaceholder(this.model.selectedImage.shapes.resourcesShapeTypes, 'description'), 'slave_shape', 'description', 'json');
-    // }
+  private getTemplates(project) {
+    this.userResourceService.getComputationalTemplates(project).subscribe(
+      clusterTypes => {
+        this.clusterTypes = clusterTypes;
+        this.selectedImage = clusterTypes[0];
+        this.getComputationalResourceLimits();
+        this.filterShapes();
+        this.resourceForm.get('template_name').setValue(this.selectedImage.template_name)
+      });
   }
 
   private filterShapes(): void {
@@ -337,22 +252,35 @@ export class ComputationalResourceCreateDialogComponent implements OnInit {
         }, {});
 
       if (DICTIONARY.cloud_provider !== 'azure') {
-        const images = this.cluster_types.filter(image => image.image === 'docker.dlab-dataengine');
-        this.cluster_types = images;
-        // (images.length > 0) ? this.model.setSelectedClusterType(0) : this.model.availableTemplates = false;
+        const images = this.clusterTypes.filter(image => image.image === 'docker.dlab-dataengine');
+        this.clusterTypes = images;
       }
       this.selectedImage.computation_resources_shapes = filtered;
     }
   }
 
-  private getTemplates(project) {
-    this.userResourceService.getComputationalTemplates(project).subscribe(
-      cluster_types => {
-        this.cluster_types = cluster_types;
-        this.selectedImage = cluster_types[0];
-        this.getComputationalResourceLimits();
-        this.filterShapes();
-        this.resourceForm.get('template_name').setValue(this.selectedImage.template_name)
-      });
+  private filterAvailableSpots() {
+    const filtered = JSON.parse(JSON.stringify(this.selectedImage.computation_resources_shapes));
+    for (const item in this.selectedImage.computation_resources_shapes) {
+      filtered[item] = filtered[item].filter(el => el.spot);
+      if (filtered[item].length <= 0) {
+        delete filtered[item];
+      }
+    }
+    return filtered;
+  }
+
+  private containsComputationalResource(conputational_resource_name: string): boolean {
+    if (conputational_resource_name)
+      for (let index = 0; index < this.resourcesList.length; index++) {
+        if (this.notebook_instance.name === this.resourcesList[index].name) {
+          for (let iindex = 0; iindex < this.resourcesList[index].resources.length; iindex++) {
+            const computational_name = this.resourcesList[index].resources[iindex].computational_name.toString().toLowerCase();
+            if (CheckUtils.delimitersFiltering(conputational_resource_name) === CheckUtils.delimitersFiltering(computational_name))
+              return true;
+          }
+        }
+      }
+    return false;
   }
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
index 68d7125..80b35f0 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.html
@@ -19,8 +19,8 @@
 
 <table class="dashboard_table data-grid">
   <tr>
-    <th *ngFor="let column of filteringColumns"
-        ngClass="{{column.className || ''}}" [hidden]="column.name === 'cost' && !healthStatus?.billingEnabled">
+    <th *ngFor="let column of filteringColumns" ngClass="{{column.className || ''}}"
+      [hidden]="column.name === 'cost' && !healthStatus?.billingEnabled">
       {{column.title}}
       <button mat-icon-button *ngIf="column.filtering" aria-label="More" class="ar" (click)="toggleFilterRow()">
         <i class="material-icons">
@@ -33,16 +33,21 @@
 
   <tr *ngIf="filteredEnvironments && collapseFilterRow" class="filter-row">
     <td>
-      <input placeholder="Filter by environment name" type="text" class="form-control filter-field" [value]="filterForm.name" (input)="filterForm.name = $event.target.value" />
+      <input placeholder="Filter by environment name" type="text" class="form-control filter-field"
+        [value]="filterForm.name" (input)="filterForm.name = $event.target.value" />
     </td>
     <td>
-      <multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'statuses'" [items]="filterConfiguration.statuses" [model]="filterForm.statuses"></multi-select-dropdown>
+      <multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'statuses'"
+        [items]="filterConfiguration.statuses" [model]="filterForm.statuses"></multi-select-dropdown>
     </td>
     <td>
-      <multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="DICTIONARY.cloud_provider === 'aws' ? 'shapes': 'sizes'" [items]="filterConfiguration.shapes" [model]="filterForm.shapes"></multi-select-dropdown>
+      <multi-select-dropdown (selectionChange)="onUpdate($event)"
+        [type]="DICTIONARY.cloud_provider === 'aws' ? 'shapes': 'sizes'" [items]="filterConfiguration.shapes"
+        [model]="filterForm.shapes"></multi-select-dropdown>
     </td>
     <td>
-      <multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'resources'" [items]="filterConfiguration.resources" [model]="filterForm.resources"></multi-select-dropdown>
+      <multi-select-dropdown (selectionChange)="onUpdate($event)" [type]="'resources'"
+        [items]="filterConfiguration.resources" [model]="filterForm.resources"></multi-select-dropdown>
     </td>
     <td *ngIf="healthStatus?.billingEnabled"></td>
     <td>
@@ -51,37 +56,44 @@
           <i class="material-icons">close</i>
         </button>
 
-        <button mat-icon-button class="btn apply" (click)="applyFilter_btnClick(filterForm)" [disabled]="filteredEnvironments.length == 0 && !filtering">
+        <button mat-icon-button class="btn apply" (click)="applyFilter_btnClick(filterForm)"
+          [disabled]="filteredEnvironments.length == 0 && !filtering">
           <i class="material-icons" [ngClass]="{'not-allowed': filteredEnvironments.length == 0 && !filtering}">done</i>
         </button>
       </div>
     </td>
   </tr>
 
-  <tr *ngIf="(!filteredEnvironments) && !filtering || (filteredEnvironments.length == 0) && !filtering" class="message_block">
-    <td [colSpan]="!healthStatus?.billingEnabled ? filteringColumns.length -1 : filteringColumns.length">To start working, please, create new environment</td>
+  <tr *ngIf="(!filteredEnvironments) && !filtering || (filteredEnvironments.length == 0) && !filtering"
+    class="message_block">
+    <td [colSpan]="!healthStatus?.billingEnabled ? filteringColumns.length -1 : filteringColumns.length">To start
+      working, please, create new environment</td>
   </tr>
 
   <tr *ngIf="(filteredEnvironments.length == 0) && filtering" class="message_block">
-    <td [colSpan]="!healthStatus?.billingEnabled ? filteringColumns.length -1 : filteringColumns.length">No matches found</td>
+    <td [colSpan]="!healthStatus?.billingEnabled ? filteringColumns.length -1 : filteringColumns.length">No matches
+      found</td>
   </tr>
 
-  <tr *ngFor="let env of filteredEnvironments;" class="dashboard_table_body" [ngClass]="{'dropdown-outscreen': isOutscreenDropdown}">
+  <tr *ngFor="let env of filteredEnvironments;" class="dashboard_table_body"
+    [ngClass]="{'dropdown-outscreen': isOutscreenDropdown}">
     <td (click)="printDetailEnvironmentModal(env)">{{env.name}}</td>
     <td class="status" ngClass="{{env.status.toLowerCase() || ''}}">{{ env.status | underscoreless }}</td>
     <td>{{env.shape}}</td>
     <td>
-      <computational-resources-list [resources]="env.resources" [environment]="env" (buildGrid)="buildGrid($event)"></computational-resources-list>
+      <computational-resources-list [resources]="env.resources" [environment]="env" (buildGrid)="buildGrid($event)">
+      </computational-resources-list>
     </td>
     <td *ngIf="healthStatus?.billingEnabled">
       <span class="total_cost">{{ env.cost || 'N/A' }} {{ env.currency_code || '' }}</span>
-      <span (click)="env.billing && printCostDetails(env)" class="currency_details" [ngClass]="{ 'not-allowed' : !env.billing }">
+      <span (click)="env.billing && printCostDetails(env)" class="currency_details"
+        [ngClass]="{ 'not-allowed' : !env.billing }">
         <i class="material-icons">help_outline</i>
       </span>
     </td>
     <td class="settings">
       <span #settings (click)="actions.toggle($event, settings)" class="actions"
-            [ngClass]="{ 'disabled': env.status.toLowerCase() === 'creating' }">
+        [ngClass]="{ 'disabled': env.status.toLowerCase() === 'creating' }">
       </span>
 
       <bubble-up #actions class="list-menu" position="bottom-left" alternative="top-left">
@@ -90,50 +102,48 @@
                 && env.status !== 'terminating'
                 && env.status !== 'terminated'
                 && env.status !== 'creating image'">
-            <li *ngIf="env.status !== 'stopped' && env.status !== 'stopping' && env.status !== 'starting' && env.status !== 'creating image'"
-                matTooltip="Unable to stop notebook because at least one computational resource is in progress"
-                matTooltipPosition="above"
-                [matTooltipDisabled]="!isResourcesInProgress(env)">
-                <div (click)="exploratoryAction(env, 'stop')" [ngClass]="{'not-allowed': isResourcesInProgress(env) }">
-                  <i class="material-icons">pause_circle_outline</i>
-                  <span>Stop</span>
-                </div>
+            <li
+              *ngIf="env.status !== 'stopped' && env.status !== 'stopping' && env.status !== 'starting' && env.status !== 'creating image'"
+              matTooltip="Unable to stop notebook because at least one computational resource is in progress"
+              matTooltipPosition="above" [matTooltipDisabled]="!isResourcesInProgress(env)">
+              <div (click)="exploratoryAction(env, 'stop')" [ngClass]="{'not-allowed': isResourcesInProgress(env) }">
+                <i class="material-icons">pause_circle_outline</i>
+                <span>Stop</span>
+              </div>
             </li>
             <li *ngIf="env.status.toLowerCase() === 'stopped' || env.status.toLowerCase() === 'stopping'"
-                matTooltip="Unable to run notebook until it will be stopped"
-                matTooltipPosition="above"
-                [matTooltipDisabled]="!isResourcesInProgress(env) && env.status.toLowerCase() !== 'stopping'">
-              <div (click)="exploratoryAction(env, 'run')" [ngClass]="{'not-allowed': isResourcesInProgress(env) || env.status.toLowerCase() === 'stopping' }">
+              matTooltip="Unable to run notebook until it will be stopped" matTooltipPosition="above"
+              [matTooltipDisabled]="!isResourcesInProgress(env) && env.status.toLowerCase() !== 'stopping'">
+              <div (click)="exploratoryAction(env, 'run')"
+                [ngClass]="{'not-allowed': isResourcesInProgress(env) || env.status.toLowerCase() === 'stopping' }">
                 <i class="material-icons">play_circle_outline</i>
                 <span>Run</span>
               </div>
             </li>
             <li *ngIf="env.status.toLowerCase() === 'running' || env.status.toLowerCase() === 'stopped'"
-                matTooltip="Unable to terminate notebook because at least one computational resource is in progress"
-                matTooltipPosition="above"
-                [matTooltipDisabled]="!isResourcesInProgress(env)">
-              <div (click)="exploratoryAction(env, 'terminate')" [ngClass]="{'not-allowed': isResourcesInProgress(env) }">
+              matTooltip="Unable to terminate notebook because at least one computational resource is in progress"
+              matTooltipPosition="above" [matTooltipDisabled]="!isResourcesInProgress(env)">
+              <div (click)="exploratoryAction(env, 'terminate')"
+                [ngClass]="{'not-allowed': isResourcesInProgress(env) }">
                 <i class="material-icons">phonelink_off</i>
                 <span>Terminate</span>
               </div>
             </li>
-            <li (click)="exploratoryAction(env, 'deploy')"
-                *ngIf="env.status != 'stopping'
+            <li (click)="exploratoryAction(env, 'deploy')" *ngIf="env.status != 'stopping'
                 && env.status !== 'stopped'
                 && env.status !== 'starting'
                 && env.status !== 'creating image'">
               <i class="material-icons">memory</i>
               <span>Add compute</span>
             </li>
-            <li (click)="exploratoryAction(env, 'schedule')"
-                *ngIf="env.status.toLowerCase() === 'running'
+            <li (click)="exploratoryAction(env, 'schedule')" *ngIf="env.status.toLowerCase() === 'running'
                 || env.status.toLowerCase() === 'stopped'">
-                <i class="material-icons">schedule</i>
+              <i class="material-icons">schedule</i>
               <span>Scheduler</span>
             </li>
           </div>
           <li (click)="exploratoryAction(env, 'ami')"
-              *ngIf="env.status === 'running' && DICTIONARY.cloud_provider !== 'gcp'">
+            *ngIf="env.status === 'running' && DICTIONARY.cloud_provider !== 'gcp'">
             <i class="material-icons">view_module</i>
             <span>Create {{ DICTIONARY.image }}</span>
           </li>
@@ -141,12 +151,12 @@
             <i class="material-icons">developer_board</i>
             <span>Manage libraries</span>
           </li>
-          <li>
-            <a target="_blank" [routerLink]="['/terminal',  env.ip]" class="navigate">
+          <!-- <li>
+            <a target="_blank" [routerLink]="['/terminal', env.ip]" class="navigate">
               <i class="material-icons">laptop</i>
               <span>Open terminal</span>
             </a>
-          </li>
+          </li> -->
         </ul>
       </bubble-up>
     </td>


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