You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@datalab.apache.org by hs...@apache.org on 2022/06/29 12:27:57 UTC

[incubator-datalab] 01/01: [DATALAB-2842] added logic by disable recreate button

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

hshpak pushed a commit to branch fix/DATALAB-2842/button-visibility-of-edge-node-recreation
in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git

commit f5e206073610ae80cff54dba8610061828e9e2b6
Author: Hennadii_Shpak <bo...@gmail.com>
AuthorDate: Wed Jun 22 13:46:14 2022 +0300

    [DATALAB-2842] added logic by disable recreate button
---
 .../create-odahu-cluster.component.ts              |   2 +-
 .../webapp/src/app/administration/project/index.ts |   8 +-
 .../administration/project/project-data.service.ts |   2 +-
 .../project/project-form/project-form.component.ts |   8 +-
 .../project-list/project-list.component.html       |  31 +++--
 .../project-list/project-list.component.scss       |  19 ++-
 .../project/project-list/project-list.component.ts | 141 ++++++++++++---------
 .../administration/project/project.component.ts    |  29 +----
 .../project/project.config.ts}                     |  16 +--
 .../project/project.model.ts}                      |  29 +++--
 .../webapp/src/app/core/directives/index.ts        |   8 +-
 .../directives/is-endpoint-active.directive.ts     |  60 +++++++++
 .../core/util/{index.ts => checkEndpointList.ts}   |  15 +--
 .../webapp/src/app/core/util/checkUtils.ts         |   2 +-
 .../resources/webapp/src/app/core/util/index.ts    |   1 +
 .../create-environment.component.ts                |  15 ++-
 .../resources-grid/resources-grid.component.ts     |   2 +-
 .../notification-dialog.component.ts               |   3 +-
 .../src/app/shared/navbar/navbar.component.html    |   4 +-
 19 files changed, 248 insertions(+), 147 deletions(-)

diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts
index a4f8360fa..e33e2e97e 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/odahu/create-odahu-claster/create-odahu-cluster.component.ts
@@ -22,11 +22,11 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms';
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { ToastrService } from 'ngx-toastr';
 
-import { Project } from '../../project/project.component';
 import { ProjectService, OdahuDeploymentService } from '../../../core/services';
 
 import { DICTIONARY } from '../../../../dictionary/global.dictionary';
 import {CheckUtils, PATTERNS} from '../../../core/util';
+import { Project } from '../../project/project.model';
 
 
 @Component({
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts
index 8e57f0fb0..d1fd9d7ff 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/index.ts
@@ -23,15 +23,16 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 
 import { MaterialModule } from '../../shared/material.module';
 import { FormControlsModule } from '../../shared/form-controls';
-import { UnderscorelessPipeModule } from '../../core/pipes/underscoreless-pipe';
 
 import { ProjectFormComponent } from './project-form/project-form.component';
 import { ProjectListComponent } from './project-list/project-list.component';
 
 import { ProjectComponent, EditProjectComponent } from './project.component';
 import { ProjectDataService } from './project-data.service';
-import {BubbleModule} from "../../shared/bubble";
 import {InformMessageModule} from '../../shared/inform-message';
+import { DirectivesModule } from '../../core/directives';
+import { BubbleModule } from '../../shared';
+import { UnderscorelessPipeModule } from '../../core/pipes';
 
 @NgModule({
     imports: [
@@ -42,7 +43,8 @@ import {InformMessageModule} from '../../shared/inform-message';
         FormControlsModule,
         UnderscorelessPipeModule,
         BubbleModule,
-        InformMessageModule
+        InformMessageModule,
+        DirectivesModule
     ],
   declarations: [ProjectComponent, EditProjectComponent, ProjectFormComponent, ProjectListComponent],
   entryComponents: [EditProjectComponent],
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts
index 0cb20d26b..ad9e5c380 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-data.service.ts
@@ -22,7 +22,7 @@ import { BehaviorSubject, of } from 'rxjs';
 import { mergeMap} from 'rxjs/operators';
 
 import { ProjectService, EndpointService } from '../../core/services';
-import { Project } from './project.component';
+import { Project } from './project.model';
 
 @Injectable()
 export class ProjectDataService {
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts
index b5cd1b84d..d6fe75cde 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-form/project-form.component.ts
@@ -27,9 +27,9 @@ import { Subscription } from 'rxjs';
 import { ProjectService, RolesGroupsService, EndpointService, UserAccessKeyService } from '../../../core/services';
 import { ProjectDataService } from '../project-data.service';
 import { CheckUtils, FileUtils, PATTERNS } from '../../../core/util';
-import { Project } from '../project.component';
 import { DICTIONARY } from '../../../../dictionary/global.dictionary';
 import {ConfirmationDialogComponent} from '../../../shared/modal-dialog/confirmation-dialog';
+import { Project } from '../project.model';
 
 export interface GenerateKey { privateKey: string; publicKey: string; }
 
@@ -88,7 +88,7 @@ export class ProjectFormComponent implements OnInit {
         () => {
           this.toastr.success('Project updated successfully!', 'Success!');
           this.update.emit();
-        }, 
+        },
         error => this.toastr.error(error.message || 'Project update failed!', 'Oops!')
       );
   }
@@ -96,7 +96,7 @@ export class ProjectFormComponent implements OnInit {
   public confirm(data) {
     if (this.item) {
       const deletedGroups = this.item.groups.filter((v) => !(this.projectForm.value.groups.includes(v)));
-      
+
       if (deletedGroups.length) {
         this.dialog.open(ConfirmationDialogComponent, {
           data: {notebook: deletedGroups, type: 5, manageAction: true}, panelClass: 'modal-md'
@@ -170,7 +170,7 @@ export class ProjectFormComponent implements OnInit {
 
 
   public selectOptions(list, key, select?) {
-    const filter = key === 'endpoints' ? list.filter(el => el.status === 'ACTIVE').map(el => el.name) : list
+    const filter = key === 'endpoints' ? list.filter(el => el.status === 'ACTIVE').map(el => el.name) : list;
     this.projectForm.controls[key].setValue(select ? filter : []);
   }
 
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html
index bf4c8f739..ce81d953a 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.html
@@ -28,7 +28,7 @@
       <td mat-cell *matCellDef="let element" class="groups">
         <div class="mat-chip-list-wrap scrolling">
           <mat-chip-list>
-            <mat-chip 
+            <mat-chip
               *ngFor="let group of element.groups"
               [matTooltip]="group"
               matTooltipPosition="above"
@@ -45,7 +45,7 @@
       <th mat-header-cell *matHeaderCellDef class="endpoints">
         <span class="label-endpoint"> Endpoint </span>
         <span class="label-endpoint-status"> Endpoint status </span>
-        <span class="label-status">Edge node status </span>
+        <span class="label-status">Edge node status</span>
       </th>
       <td mat-cell *matCellDef="let element" class="source endpoints">
         <div *ngIf="!element.endpoints?.length; else list">
@@ -63,9 +63,9 @@
                 {{ (endpoint.endpointStatus | titlecase) || 'N/A'}}
               </span>
             </div>
-            
+
             <span class="status resource-status" [ngClass]="endpoint?.status.toLowerCase() || ''">
-              {{ endpoint?.status.toLowerCase() }}
+              {{ endpoint?.status | titlecase }}
             </span>
           </div>
         </ng-template>
@@ -93,11 +93,24 @@
                 Stop edge node
               </a>
             </li>
-            <li class="project-seting-item " *ngIf="element.areTerminatedNode && isEndpointAvailable" (click)="openEdgeDialog('recreate', element)">
-              <i class="material-icons">refresh</i>
-              <a class="action">
-                Recreate edge node
-              </a>
+            <li
+                *ngIf="(element.areTerminatedNode || element.areStoppedNode) && isEndpointAvailable"
+                (click)="openEdgeDialog('recreate', element)"
+
+            >
+              <button class="project-setting-button"
+                      #recreateBtn
+                      datalabIsEndpointActive
+                      [endpointList]="element.endpoints"
+                      [matTooltip]="'Unable to recreate edge node if endpoint status is not active.'"
+                      matTooltipPosition="above"
+                      [matTooltipDisabled]="!isRecreateBtnDisabled(element.endpoints)"
+              >
+                <i class="material-icons">refresh</i>
+                <a class="action" >
+                  Recreate edge node
+                </a>
+              </button>
             </li>
             <li class="project-seting-item " *ngIf="element.areStoppedNode || element.areRunningNode" (click)="openEdgeDialog('terminate', element)">
               <i class="material-icons">phonelink_off</i>
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss
index 836b822f6..0abea7e84 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.scss
@@ -138,7 +138,8 @@ td.settings {
   }
 }
 
-.project-seting-item {
+.project-seting-item,
+.project-setting-button{
   display: flex;
   align-items: center;
   padding: 10px;
@@ -157,6 +158,18 @@ td.settings {
   }
 }
 
+.project-setting-button {
+  width: 100%;
+  background-color: transparent;
+  border: none;
+}
+
+.disabled-button {
+  color: rgba(0,0,0,0.26);
+  background-color: rgba(0,0,0,0.12);
+  cursor: not-allowed;
+}
+
 .material-icons {
   font-size: 18px;
   padding-top: 1px;
@@ -171,11 +184,11 @@ td.settings {
 }
 
 .actions-list {
-  padding: 10px 15px;
+  padding: 10px 0px;
 }
 
 .mat-chip {
   max-width: 200px !important;
   white-space: nowrap;
   display: inline-block;
-}
\ No newline at end of file
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts
index ca2c331a4..0086cb918 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project-list/project-list.component.ts
@@ -17,33 +17,37 @@
  * under the License.
  */
 
-import { Component, OnInit, Output, EventEmitter, OnDestroy, Inject, Input } from '@angular/core';
+import { Component, OnInit, Output, EventEmitter, OnDestroy, Inject, Input, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
 import { ToastrService } from 'ngx-toastr';
 import { MatTableDataSource } from '@angular/material/table';
 import { Subscription } from 'rxjs';
 import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
 
 import { ProjectDataService } from '../project-data.service';
-import { Project, Endpoint } from '../project.component';
 import {ProgressBarService} from '../../../core/services/progress-bar.service';
 import {EdgeActionDialogComponent} from '../../../shared/modal-dialog/edge-action-dialog';
 import { EndpointService } from '../../../core/services';
+import { Endpoint, ModifiedEndpoint, Project } from '../project.model';
+import { checkEndpointList } from '../../../core/util';
+import { EndpointStatus } from '../project.config';
 
 @Component({
   selector: 'project-list',
   templateUrl: './project-list.component.html',
   styleUrls: ['./project-list.component.scss', '../../../resources/computational/computational-resources-list/computational-resources-list.component.scss']
 })
-export class ProjectListComponent implements OnInit, OnDestroy {
+export class ProjectListComponent implements OnInit, OnDestroy, AfterViewInit {
+  @Input() isProjectAdmin: boolean;
+  @Output() editItem: EventEmitter<{}> = new EventEmitter();
+  @Output() toggleStatus: EventEmitter<{}> = new EventEmitter();
+
+  @ViewChild('recreateBtn') recreateBtn: ElementRef;
 
   displayedColumns: string[] = ['name', 'groups', 'endpoints', 'actions'];
   dataSource: Project[] | any = [];
   projectList: Project[];
   isEndpointAvailable: boolean;
 
-  @Input() isProjectAdmin: boolean;
-  @Output() editItem: EventEmitter<{}> = new EventEmitter();
-  @Output() toggleStatus: EventEmitter<{}> = new EventEmitter();
   private subscriptions: Subscription = new Subscription();
 
   constructor (
@@ -56,52 +60,23 @@ export class ProjectListComponent implements OnInit, OnDestroy {
     public dialog: MatDialog,
   ) { }
 
-  ngOnInit() {
+  ngOnInit(): void {
     this.getProjectList();
     this.getEndpointList();
   }
 
-  ngOnDestroy() {
-    this.subscriptions.unsubscribe();
-  }
+  ngAfterViewInit(): void {
 
-  private getProjectList() {
-    this.progressBarService.startProgressBar();
-    this.subscriptions.add(this.projectDataService._projects
-      .subscribe(
-        (value: Project[]) => {
-          this.projectList = value;
-          if (this.projectList) {
-            this.projectList.forEach(project => {
-              project.areRunningNode = this.areResoursesInStatuses(project.endpoints, ['RUNNING']);
-              project.areStoppedNode = this.areResoursesInStatuses(project.endpoints, ['STOPPED']);
-              project.areTerminatedNode = this.areResoursesInStatuses(project.endpoints, ['TERMINATED', 'FAILED']);
-            });
-          }
-          if (value) this.dataSource = new MatTableDataSource(value);
-          this.progressBarService.stopProgressBar();
-        }, 
-        () => this.progressBarService.stopProgressBar()
-      )
-    );
   }
 
-  private getEndpointList() {
-    this.endpointService.getEndpointsData().subscribe(
-      (response: Endpoint[] | []) => {
-        this.isEndpointAvailable = this.checkIsEndpointAvailable(response);
-      }
-    )
-  }
-
-  private checkIsEndpointAvailable(data: Endpoint[] | []): boolean {
-    return  data.length ? true : false;
+  ngOnDestroy(): void {
+    this.subscriptions.unsubscribe();
   }
 
   public showActiveInstances(): void {
     const filteredList = this.projectList.map(project => {
       project.endpoints = project.endpoints.filter((endpoint: Endpoint) => {
-        return endpoint.status !== 'TERMINATED' && endpoint.status !== 'TERMINATING' && endpoint.status !== 'FAILED'
+        return endpoint.status !== 'TERMINATED' && endpoint.status !== 'TERMINATING' && endpoint.status !== 'FAILED';
       });
       return project;
     });
@@ -113,37 +88,83 @@ export class ProjectListComponent implements OnInit, OnDestroy {
     this.editItem.emit(item);
   }
 
-  public openEdgeDialog(action, project) {
-    const endpoints = project.endpoints.filter(endpoint => {
+  public openEdgeDialog(action: string, project: Project) {
+    const endpoints = this.getFilteredEndpointList(action, project);
+
+    if (action === 'terminate' && endpoints.length === 1) {
+      this.toggleStatus.emit({ project, endpoint: endpoints, action, oneEdge: true });
+    } else {
+      this.dialog.open(EdgeActionDialogComponent, { data: { type: action, item: endpoints }, panelClass: 'modal-sm' })
+        .afterClosed().subscribe(
+        endpoint => {
+          if (endpoint && endpoint.length) {
+            this.toggleStatus.emit({project, endpoint, action});
+          }
+        },
+        error => this.toastr.error(error.message || `Endpoint ${action} failed!`, 'Oops!')
+      );
+    }
+  }
+
+  public areResoursesInStatuses(resources, statuses: Array<string>) {
+    return resources.some(resource => statuses.some(status => resource.status === status));
+  }
+
+  isRecreateBtnDisabled(endpointList: ModifiedEndpoint[]): boolean {
+    return checkEndpointList(endpointList);
+  }
+
+  private getFilteredEndpointList(action: string, project) {
+    return project.endpoints.filter(endpoint => {
       if (action === 'stop') {
-        return endpoint.status === 'RUNNING';
+        return endpoint.status === EndpointStatus.running;
       }
       if (action === 'start') {
-        return endpoint.status === 'STOPPED';
+        return endpoint.status === EndpointStatus.stopped;
       }
       if (action === 'terminate') {
-        return endpoint.status === 'RUNNING' || endpoint.status === 'STOPPED';
+        return endpoint.status === EndpointStatus.running || endpoint.status === EndpointStatus.stopped;
       }
       if (action === 'recreate') {
-        return endpoint.status === 'TERMINATED' || endpoint.status === 'FAILED';
+        return endpoint.status === EndpointStatus.terminated || endpoint.status === EndpointStatus.failed;
       }
     });
-    if (action === 'terminate' && endpoints.length === 1) {
-      this.toggleStatus.emit({ project, endpoint: endpoints, action, oneEdge: true });
-    } else {
-      this.dialog.open(EdgeActionDialogComponent, { data: { type: action, item: endpoints }, panelClass: 'modal-sm' })
-        .afterClosed().subscribe(
-          endpoint => {
-            if (endpoint && endpoint.length) {
-              this.toggleStatus.emit({project, endpoint, action});
-            }
-          }, 
-          error => this.toastr.error(error.message || `Endpoint ${action} failed!`, 'Oops!')
-        );
+  }
+
+  private getProjectList() {
+    this.progressBarService.startProgressBar();
+    this.subscriptions.add(this.projectDataService._projects
+      .subscribe(
+        (value: Project[]) => {
+          this.modifyProjectList(value);
+          if (value) this.dataSource = new MatTableDataSource(value);
+          this.progressBarService.stopProgressBar();
+        },
+        () => this.progressBarService.stopProgressBar()
+      )
+    );
+  }
+
+  private modifyProjectList(value: Project[]): void {
+    this.projectList = value;
+    if (this.projectList) {
+      this.projectList.forEach(project => {
+        project.areRunningNode = this.areResoursesInStatuses(project.endpoints, [EndpointStatus.running]);
+        project.areStoppedNode = this.areResoursesInStatuses(project.endpoints, [EndpointStatus.stopped]);
+        project.areTerminatedNode = this.areResoursesInStatuses(project.endpoints, [EndpointStatus.terminated, EndpointStatus.failed]);
+      });
     }
   }
 
-  public areResoursesInStatuses(resources, statuses: Array<string>) {
-    return resources.some(resource => statuses.some(status => resource.status === status));
+  private getEndpointList() {
+    this.endpointService.getEndpointsData().subscribe(
+      (response: Endpoint[] | []) => {
+        this.isEndpointAvailable = this.checkIsEndpointAvailable(response);
+      }
+    );
+  }
+
+  private checkIsEndpointAvailable(data: Endpoint[] | []): boolean {
+    return  data.length ? true : false;
   }
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts
index 1409c7b80..61e486590 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts
@@ -27,23 +27,8 @@ import {HealthStatusService, ProjectService } from '../../core/services';
 import { NotificationDialogComponent } from '../../shared/modal-dialog/notification-dialog';
 import { ProjectListComponent } from './project-list/project-list.component';
 import { EnvironmentsDataService } from '../management/management-data.service';
+import { Project } from './project.model';
 
-export interface Endpoint {
-  name: string;
-  status: string;
-  edgeInfo: any;
-}
-
-export interface Project {
-  name: string;
-  endpoints: any;
-  tag: string;
-  groups: string[];
-  shared_image_enabled?: boolean;
-  areStoppedNode?: boolean;
-  areTerminatedNode?: boolean;
-  areRunningNode?: boolean;
-}
 
 @Component({
   selector: 'datalab-project',
@@ -118,8 +103,8 @@ export class ProjectComponent implements OnInit, OnDestroy {
   }
 
   public toggleStatus($event) {
-    const data = { 
-      'project_name': $event.project.name, 
+    const data = {
+      'project_name': $event.project.name,
       endpoint: $event.endpoint.map(endpoint => endpoint.name),
       'edge_status': $event.endpoint.map(endpoint => endpoint.status)[0]
     };
@@ -129,11 +114,11 @@ export class ProjectComponent implements OnInit, OnDestroy {
   private toggleStatusRequest(data, action, isOnlyOneEdge?) {
     if ( action === 'terminate') {
       const projectsResources = this.resources
-        .filter(resource => resource.project === data.project_name && resource.resource_type !== "edge node");
+        .filter(resource => resource.project === data.project_name && resource.resource_type !== 'edge node');
 
       const activeProjectsResources = projectsResources?.length ? projectsResources
         .filter(expl => expl.status !== 'terminated' && expl.status !== 'terminating' && expl.status !== 'failed') : [];
-        
+
       const termResources = data.endpoint.reduce((res, endp) => {
         res.push(...activeProjectsResources.filter(resource => resource.endpoint === endp));
         return res;
@@ -143,7 +128,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
         this.edgeNodeAction(data, action);
       } else {
         this.dialog.open(NotificationDialogComponent, { data: {
-            type: 'terminateNode', 
+            type: 'terminateNode',
             item: {action: data, resources: termResources}
           }, panelClass: 'modal-sm' })
           .afterClosed().subscribe(result => {
@@ -161,7 +146,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
         () => {
           this.refreshGrid();
           this.toastr.success(`Edge node ${this.toEndpointAction(action)} is in progress!`, 'Processing!');
-        }, 
+        },
         error => {
           this.toastr.error(error.message, 'Oops!');
         }
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.config.ts
similarity index 74%
copy from services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
copy to services/self-service/src/main/resources/webapp/src/app/administration/project/project.config.ts
index 8c04f80aa..fa333d082 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.config.ts
@@ -17,12 +17,10 @@
  * under the License.
  */
 
-export * from './http-status-codes';
-export * from './sortUtils';
-export * from './helpUtils';
-export * from './errorUtils';
-export * from './dateUtils';
-export * from './fileUtils';
-export * from './checkUtils';
-export * from './patterns';
-export * from './http-methods';
+export enum EndpointStatus {
+  terminated = 'TERMINATED',
+  terminating = 'TERMINATING',
+  failed = 'FAILED',
+  running = 'RUNNING',
+  stopped = 'STOPPED',
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.model.ts
similarity index 66%
copy from services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
copy to services/self-service/src/main/resources/webapp/src/app/administration/project/project.model.ts
index 8c04f80aa..bfdc79030 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.model.ts
@@ -17,12 +17,23 @@
  * under the License.
  */
 
-export * from './http-status-codes';
-export * from './sortUtils';
-export * from './helpUtils';
-export * from './errorUtils';
-export * from './dateUtils';
-export * from './fileUtils';
-export * from './checkUtils';
-export * from './patterns';
-export * from './http-methods';
+export interface Endpoint {
+  name: string;
+  status: string;
+  edgeInfo: any;
+}
+
+export interface ModifiedEndpoint extends Endpoint {
+  endpointStatus?: 'ACTIVE' | 'INACTIVE';
+}
+
+export interface Project {
+  name: string;
+  endpoints: any;
+  tag: string;
+  groups: string[];
+  shared_image_enabled?: boolean;
+  areStoppedNode?: boolean;
+  areTerminatedNode?: boolean;
+  areRunningNode?: boolean;
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts
index 83a0eaee4..9f8dcb2b2 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/index.ts
@@ -22,14 +22,12 @@ import { NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { ClickOutsideDirective } from './click-outside.directive';
 import { ScrollDirective } from './scrollTo.directive';
-
-export * from './scrollTo.directive';
-export * from './click-outside.directive';
+import { IsEndpointsActiveDirective } from './is-endpoint-active.directive';
 
 @NgModule({
   imports: [CommonModule],
-  declarations: [ClickOutsideDirective, ScrollDirective],
-  exports: [ClickOutsideDirective, ScrollDirective]
+  declarations: [ClickOutsideDirective, ScrollDirective, IsEndpointsActiveDirective],
+  exports: [ClickOutsideDirective, ScrollDirective, IsEndpointsActiveDirective]
 })
 
 export class DirectivesModule { }
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts
new file mode 100644
index 000000000..0f9db68bd
--- /dev/null
+++ b/services/self-service/src/main/resources/webapp/src/app/core/directives/is-endpoint-active.directive.ts
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { Directive, ElementRef, HostListener, Input, OnInit, Renderer2 } from '@angular/core';
+
+import { ModifiedEndpoint } from '../../administration/project/project.model';
+import { checkEndpointList } from '../util';
+
+
+@Directive({
+  selector: '[datalabIsEndpointActive]'
+})
+export class IsEndpointsActiveDirective implements OnInit {
+  @Input() endpointList: ModifiedEndpoint[];
+
+  private isButtonDisabled: boolean = false;
+
+  constructor (
+    private el: ElementRef,
+    private renderer: Renderer2
+  ) { }
+
+  ngOnInit(): void {
+    this.checkEndpointList(this.endpointList);
+    this.setStyle();
+  }
+
+  @HostListener('click', ['$event'])
+  onClick(event: Event): void {
+    if (this.isButtonDisabled) {
+      event.stopPropagation();
+    }
+  }
+
+  private setStyle() {
+    if (this.isButtonDisabled) {
+      this.renderer.addClass(this.el.nativeElement, 'disabled-button');
+    }
+  }
+
+  private checkEndpointList(endpointList: ModifiedEndpoint[]): void {
+    this.isButtonDisabled = checkEndpointList(endpointList);
+  }
+}
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts
similarity index 72%
copy from services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
copy to services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts
index 8c04f80aa..99f927ddc 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/util/checkEndpointList.ts
@@ -17,12 +17,9 @@
  * under the License.
  */
 
-export * from './http-status-codes';
-export * from './sortUtils';
-export * from './helpUtils';
-export * from './errorUtils';
-export * from './dateUtils';
-export * from './fileUtils';
-export * from './checkUtils';
-export * from './patterns';
-export * from './http-methods';
+import { ModifiedEndpoint } from '../../administration/project/project.model';
+
+export const checkEndpointList = (endpointList: ModifiedEndpoint[]): boolean => {
+  return endpointList.every(({status, endpointStatus}) => (status === 'TERMINATED' || status === 'FAILED')
+    && endpointStatus === 'INACTIVE');
+};
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts
index d1e6f535e..ca3e5a2c2 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/util/checkUtils.ts
@@ -28,7 +28,7 @@ export class CheckUtils {
     STOPPING: 'DISCONNECTING',
     STOPPED: 'DISCONNECTED'
   };
-  
+
   public static isJSON(str) {
     try {
       JSON.parse(str);
diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
index 8c04f80aa..b00239022 100644
--- a/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/core/util/index.ts
@@ -26,3 +26,4 @@ export * from './fileUtils';
 export * from './checkUtils';
 export * from './patterns';
 export * from './http-methods';
+export * from './checkEndpointList';
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts
index d0b107ea5..debc6515c 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/create-environment/create-environment.component.ts
@@ -22,7 +22,6 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms';
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 import { ToastrService } from 'ngx-toastr';
 
-import { Project } from '../../../administration/project/project.component';
 import { UserResourceService, ProjectService } from '../../../core/services';
 import { CheckUtils, SortUtils, HTTP_STATUS_CODES, PATTERNS, HelpUtils } from '../../../core/util';
 import { DICTIONARY } from '../../../../dictionary/global.dictionary';
@@ -30,6 +29,7 @@ import { CLUSTER_CONFIGURATION } from '../../computational/computational-resourc
 import { tap } from 'rxjs/operators';
 import { timer } from 'rxjs';
 import { TemplateName } from '../../../core/models';
+import { Project } from '../../../administration/project/project.model';
 
 @Component({
   selector: 'create-environment',
@@ -181,7 +181,8 @@ export class ExploratoryEnvironmentCreateComponent implements OnInit {
 
       this.gpuTypes = template?.computationGPU ? HelpUtils.sortGpuTypes(template.computationGPU) : [];
 
-      if(template?.image === 'docker.datalab-tensor' /**|| template?.image === 'docker.datalab-jupyter-conda'|| template?.image === 'docker.datalab-jupyter-gpu' */|| template?.image === 'docker.datalab-deeplearning') {
+      // tslint:disable-next-line:max-line-length
+      if (template?.image === 'docker.datalab-tensor' /**|| template?.image === 'docker.datalab-jupyter-conda'|| template?.image === 'docker.datalab-jupyter-gpu' */|| template?.image === 'docker.datalab-deeplearning') {
         this.addGpuFields();
       }
     }
@@ -272,9 +273,9 @@ export class ExploratoryEnvironmentCreateComponent implements OnInit {
   }
 
   public setInstanceSize() {
-    const {image, computationGPU} = this.currentTemplate
-    if(image === this.templateName.jupyterJpu) {
-      this.createExploratoryForm.get('gpu_count').setValue(computationGPU[0])
+    const {image, computationGPU} = this.currentTemplate;
+    if (image === this.templateName.jupyterJpu) {
+      this.createExploratoryForm.get('gpu_count').setValue(computationGPU[0]);
     } else {
         const controls = ['gpu_type', 'gpu_count'];
         controls.forEach(control => {
@@ -329,9 +330,9 @@ export class ExploratoryEnvironmentCreateComponent implements OnInit {
         (res: any) => {
           this.images = res.filter(el => el.status === 'CREATED');
 
-          if(this.selectedCloud === 'gcp' && this.currentTemplate.image === 'docker.datalab-deeplearning') {
+          if (this.selectedCloud === 'gcp' && this.currentTemplate.image === 'docker.datalab-deeplearning') {
             this.currentTemplate.exploratory_environment_images = this.currentTemplate.exploratory_environment_images.map(image => {
-              return {name: image['Image family'] ?? image.name, description: image['Description'] ?? image.description}
+              return {name: image['Image family'] ?? image.name, description: image['Description'] ?? image.description};
             });
             this.images.push(...this.currentTemplate.exploratory_environment_images);
           }
diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
index 28b79ecd8..4677f57d2 100644
--- a/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources-grid/resources-grid.component.ts
@@ -17,7 +17,6 @@
  * under the License.
  */
 
-import { Project } from '../../administration/project/project.component';
 import {
   Component,
   EventEmitter,
@@ -47,6 +46,7 @@ import { NotebookModel } from '../exploratory/notebook.model';
 import { AuditService } from '../../core/services/audit.service';
 import { CompareUtils } from '../../core/util/compareUtils';
 import { OdahuActionDialogComponent } from '../../shared/modal-dialog/odahu-action-dialog';
+import { Project } from '../../administration/project/project.model';
 
 export interface SharedEndpoint {
   edge_node_ip: string;
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts
index 2cdee43fc..4f532c0e8 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/notification-dialog.component.ts
@@ -19,7 +19,8 @@
 
 import { Component, Inject } from '@angular/core';
 import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
-import {Endpoint} from '../../../administration/project/project.component';
+
+import { Endpoint } from '../../../administration/project/project.model';
 
 @Component({
   selector: 'notification-dialog',
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
index 4ec0819a6..8ae321ffe 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/navbar/navbar.component.html
@@ -28,8 +28,8 @@
       <span class="line"></span>
     </button>
 
-    <a [routerLink]="['/resources_list']" class="navbar-logo">
-      <img src="assets/svg/logo.svg" alt="">
+    <a [routerLink]="['/instances']" class="navbar-logo">
+      <img src="assets/svg/logo.svg" alt="datalab">
     </a>
   </div>
 


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