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

[incubator-dlab] branch develop updated: [Dlab 1321]: Conveyed all resources which will be terminated on confirmation dialog (#493)

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 87ad55a  [Dlab 1321]: Conveyed all resources which will be terminated on confirmation dialog (#493)
87ad55a is described below

commit 87ad55a302741bf07f2746e8bb11eedf636ca8ec
Author: Dmytro Gnatyshyn <42...@users.noreply.github.com>
AuthorDate: Mon Dec 16 13:02:17 2019 +0200

    [Dlab 1321]: Conveyed all resources which will be terminated on confirmation dialog (#493)
    
    * [DLAB-1321]: Conveyed all resources which will be terminated on a confirmation dialog
---
 .../epam/dlab/backendapi/dao/ExploratoryDAO.java   |  18 ++-
 .../com/epam/dlab/backendapi/dao/ProjectDAO.java   |   2 +
 .../epam/dlab/backendapi/dao/ProjectDAOImpl.java   |   5 +
 .../backendapi/resources/EndpointResource.java     |   6 +-
 .../dlab/backendapi/service/EndpointService.java   |   6 +-
 .../dlab/backendapi/service/ProjectService.java    |   4 +
 .../service/impl/EndpointServiceImpl.java          |  36 ++++-
 .../service/impl/ProjectServiceImpl.java           |  42 ++++--
 .../management/endpoints/endpoints.component.ts    |  41 +++++-
 .../manage-environment-dilog.component.ts          |   4 +-
 .../administration/project/project.component.ts    |   2 +-
 .../resources-grid/resources-grid.component.ts     |   1 -
 .../modal-dialog/notification-dialog/index.ts      |   3 +-
 .../notification-dialog.component.ts               | 161 +++++++++++++--------
 14 files changed, 239 insertions(+), 92 deletions(-)

diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ExploratoryDAO.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ExploratoryDAO.java
index 944c155..cba6b74 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ExploratoryDAO.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ExploratoryDAO.java
@@ -68,6 +68,7 @@ public class ExploratoryDAO extends BaseDAO {
 	public static final String EXPLORATORY_NOT_FOUND_MSG = "Exploratory for user %s with name %s not found";
 	private static final String EXPLORATORY_LAST_ACTIVITY = "last_activity";
 	private static final String PROJECT = "project";
+	private static final String ENDPOINT = "endpoint";
 
 	public ExploratoryDAO() {
 		log.info("{} is initialized", getClass().getSimpleName());
@@ -187,6 +188,21 @@ public class ExploratoryDAO extends BaseDAO {
 				false);
 	}
 
+	public List<UserInstanceDTO> fetchProjectEndpointExploratoriesWhereStatusIn(String project, List<String> endpoints,
+																				List<UserInstanceStatus> exploratoryStatuses,
+																				UserInstanceStatus... computationalStatuses) {
+		final List<String> exploratoryStatusList = statusList(exploratoryStatuses);
+		final List<String> computationalStatusList = statusList(computationalStatuses);
+		return getUserInstances(
+				and(
+						eq(PROJECT, project),
+						in(ENDPOINT, endpoints),
+						or(in(STATUS, exploratoryStatusList),
+								in(COMPUTATIONAL_RESOURCES + "." + STATUS, computationalStatusList))
+				),
+				false);
+	}
+
 	/**
 	 * Finds and returns the info of all user's notebooks whose status is absent among predefined ones.
 	 *
@@ -209,7 +225,7 @@ public class ExploratoryDAO extends BaseDAO {
 		return getUserInstances(
 				and(
 						eq(PROJECT, project),
-						eq("endpoint", endpoint),
+						eq(ENDPOINT, endpoint),
 						not(in(STATUS, statusList))
 				),
 				false);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAO.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAO.java
index a9a89f5..245df5b 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAO.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAO.java
@@ -28,6 +28,8 @@ public interface ProjectDAO {
 
 	Optional<ProjectDTO> get(String name);
 
+	List<ProjectDTO> getProjectsByEndpoint(String endpointName);
+
 	boolean update(ProjectDTO projectDTO);
 
 	void remove(String name);
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAOImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAOImpl.java
index a74a07b..1e86808 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAOImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/dao/ProjectDAOImpl.java
@@ -99,6 +99,11 @@ public class ProjectDAOImpl extends BaseDAO implements ProjectDAO {
 	}
 
 	@Override
+	public List<ProjectDTO> getProjectsByEndpoint(String endpointName) {
+		return find(PROJECTS_COLLECTION, elemMatch(ENDPOINTS, eq("name", endpointName)), ProjectDTO.class);
+	}
+
+	@Override
 	public boolean update(ProjectDTO projectDTO) {
 		BasicDBObject updateProject = new BasicDBObject();
 		updateProject.put(GROUPS, projectDTO.getGroups());
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/EndpointResource.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/EndpointResource.java
index ff206df..9aebfc4 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/EndpointResource.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/resources/EndpointResource.java
@@ -101,8 +101,10 @@ public class EndpointResource {
 	@Path("{name}")
 	public Response removeEndpoint(@Parameter(hidden = true) @Auth UserInfo userInfo,
 								   @Parameter(description = "Endpoint name")
-								   @PathParam("name") String name) {
-		endpointService.remove(name);
+								   @PathParam("name") String name,
+								   @Parameter(description = "Delete endpoint only or with related resources")
+								   @QueryParam("with-resources") @DefaultValue("false") boolean withResources) {
+		endpointService.remove(userInfo, name, withResources);
 		return Response.ok().build();
 	}
 }
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/EndpointService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/EndpointService.java
index fca6c5d..61f663e 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/EndpointService.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/EndpointService.java
@@ -1,14 +1,18 @@
 package com.epam.dlab.backendapi.service;
 
+import com.epam.dlab.auth.UserInfo;
 import com.epam.dlab.backendapi.domain.EndpointDTO;
 
 import java.util.List;
 
 public interface EndpointService {
 	List<EndpointDTO> getEndpoints();
+
 	EndpointDTO get(String name);
 
 	void create(EndpointDTO endpointDTO);
 
-	void remove(String name);
+	void remove(UserInfo userInfo, String name, boolean withResources);
+
+	void removeEndpointInAllProjects(UserInfo userInfo, String endpointName);
 }
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java
index bc1dea0..e01280c 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/ProjectService.java
@@ -16,6 +16,8 @@ public interface ProjectService {
 
 	List<ProjectDTO> getProjectsWithStatus(ProjectDTO.Status status);
 
+	List<ProjectDTO> getProjectsByEndpoint(String endpointName);
+
 	void create(UserInfo userInfo, ProjectDTO projectDTO);
 
 	ProjectDTO get(String name);
@@ -37,4 +39,6 @@ public interface ProjectService {
 	void updateBudget(List<ProjectDTO> projects);
 
 	boolean isAnyProjectAssigned(UserInfo userInfo);
+
+	boolean checkExploratoriesAndComputationalProgress(String projectName, List<String> endpoints);
 }
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java
index fd9273c..2414c9f 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/EndpointServiceImpl.java
@@ -1,20 +1,28 @@
 package com.epam.dlab.backendapi.service.impl;
 
+import com.epam.dlab.auth.UserInfo;
 import com.epam.dlab.backendapi.dao.EndpointDAO;
 import com.epam.dlab.backendapi.domain.EndpointDTO;
+import com.epam.dlab.backendapi.domain.ProjectDTO;
 import com.epam.dlab.backendapi.service.EndpointService;
+import com.epam.dlab.backendapi.service.ProjectService;
+import com.epam.dlab.dto.UserInstanceStatus;
 import com.epam.dlab.exceptions.ResourceConflictException;
 import com.epam.dlab.exceptions.ResourceNotFoundException;
 import com.google.inject.Inject;
 
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
 public class EndpointServiceImpl implements EndpointService {
 	private final EndpointDAO endpointDAO;
+	private final ProjectService projectService;
 
 	@Inject
-	public EndpointServiceImpl(EndpointDAO endpointDAO) {
+	public EndpointServiceImpl(EndpointDAO endpointDAO, ProjectService projectService) {
 		this.endpointDAO = endpointDAO;
+		this.projectService = projectService;
 	}
 
 	@Override
@@ -38,7 +46,31 @@ public class EndpointServiceImpl implements EndpointService {
 	}
 
 	@Override
-	public void remove(String name) {
+	public void remove(UserInfo userInfo, String name, boolean withResources) {
+		if (withResources) {
+			removeEndpointInAllProjects(userInfo, name);
+		}
 		endpointDAO.remove(name);
 	}
+
+	@Override
+	public void removeEndpointInAllProjects(UserInfo userInfo, String endpointName) {
+		List<ProjectDTO> projects = projectService.getProjectsByEndpoint(endpointName);
+		checkProjectEndpointResourcesStatuses(projects, endpointName);
+
+		projects.forEach(project -> projectService.terminateEndpoint(userInfo, endpointName, project.getName()));
+	}
+
+	private void checkProjectEndpointResourcesStatuses(List<ProjectDTO> projects, String endpoint) {
+		boolean isTerminationEnabled = projects.stream().anyMatch(p ->
+				!projectService.checkExploratoriesAndComputationalProgress(p.getName(), Collections.singletonList(endpoint)) ||
+						p.getEndpoints().stream().anyMatch(e -> e.getName().equals(endpoint) &&
+								Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING, UserInstanceStatus.STOPPING,
+										UserInstanceStatus.TERMINATING).contains(e.getStatus())));
+
+		if (isTerminationEnabled) {
+			throw new ResourceConflictException(("Can not terminate resources of endpoint because one of project " +
+					"resource is in processing stage"));
+		}
+	}
 }
diff --git a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java
index cd9e3eb..788fafa 100644
--- a/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java
+++ b/services/self-service/src/main/java/com/epam/dlab/backendapi/service/impl/ProjectServiceImpl.java
@@ -17,7 +17,6 @@ import com.epam.dlab.backendapi.service.ProjectService;
 import com.epam.dlab.backendapi.service.SecurityService;
 import com.epam.dlab.backendapi.util.RequestBuilder;
 import com.epam.dlab.constants.ServiceConsts;
-import com.epam.dlab.dto.UserInstanceDTO;
 import com.epam.dlab.dto.UserInstanceStatus;
 import com.epam.dlab.exceptions.ResourceConflictException;
 import com.epam.dlab.exceptions.ResourceNotFoundException;
@@ -96,6 +95,11 @@ public class ProjectServiceImpl implements ProjectService {
 		return projectDAO.getProjectsWithStatus(status);
 	}
 
+	@Override
+	public List<ProjectDTO> getProjectsByEndpoint(String endpointName) {
+		return projectDAO.getProjectsByEndpoint(endpointName);
+	}
+
 	@BudgetLimited
 	@Override
 	public void create(UserInfo user, ProjectDTO projectDTO) {
@@ -122,9 +126,10 @@ public class ProjectServiceImpl implements ProjectService {
 
 	@Override
 	public void terminateProject(UserInfo userInfo, String name) {
-		checkProjectRelatedResourcesInProgress(name, TERMINATE_ACTION);
-		get(name).getEndpoints()
-				.stream()
+		List<ProjectEndpointDTO> endpoints = get(name).getEndpoints();
+		checkProjectRelatedResourcesInProgress(name, endpoints, TERMINATE_ACTION);
+
+		endpoints.stream()
 				.map(ProjectEndpointDTO::getName)
 				.forEach(endpoint -> terminateEndpoint(userInfo, endpoint, name));
 	}
@@ -144,12 +149,13 @@ public class ProjectServiceImpl implements ProjectService {
 
 	@Override
 	public void stopWithResources(UserInfo userInfo, String projectName) {
-		checkProjectRelatedResourcesInProgress(projectName, STOP_ACTION);
+		List<ProjectEndpointDTO> endpoints = get(projectName).getEndpoints();
+		checkProjectRelatedResourcesInProgress(projectName, endpoints, STOP_ACTION);
 
 		exploratoryDAO.fetchRunningExploratoryFieldsForProject(projectName).forEach(e ->
 				exploratoryService.stop(new UserInfo(e.getUser(), userInfo.getAccessToken()), e.getExploratoryName()));
 
-		get(projectName).getEndpoints().stream().filter(e -> !Arrays.asList(UserInstanceStatus.TERMINATED,
+		endpoints.stream().filter(e -> !Arrays.asList(UserInstanceStatus.TERMINATED,
 				UserInstanceStatus.TERMINATING, UserInstanceStatus.STOPPED).contains(e.getStatus()))
 				.forEach(e -> stop(userInfo, e.getName(), projectName));
 	}
@@ -190,6 +196,16 @@ public class ProjectServiceImpl implements ProjectService {
 		return projectDAO.isAnyProjectAssigned(userGroups);
 	}
 
+	@Override
+	public boolean checkExploratoriesAndComputationalProgress(String projectName, List<String> endpoints) {
+		return exploratoryDAO.fetchProjectEndpointExploratoriesWhereStatusIn(projectName, endpoints, Arrays.asList(
+				UserInstanceStatus.CREATING, UserInstanceStatus.STARTING, UserInstanceStatus.CREATING_IMAGE,
+				UserInstanceStatus.RECONFIGURING, UserInstanceStatus.STOPPING, UserInstanceStatus.TERMINATING),
+				UserInstanceStatus.CREATING, UserInstanceStatus.CONFIGURING, UserInstanceStatus.STARTING,
+				UserInstanceStatus.RECONFIGURING, UserInstanceStatus.CREATING_IMAGE, UserInstanceStatus.STOPPING,
+				UserInstanceStatus.TERMINATING).isEmpty();
+	}
+
 	private void createProjectOnCloud(UserInfo user, ProjectDTO projectDTO) {
 		try {
 			projectDTO.getEndpoints().forEach(endpoint -> createEndpoint(user, projectDTO,
@@ -220,19 +236,13 @@ public class ProjectServiceImpl implements ProjectService {
 		}
 	}
 
-	private void checkProjectRelatedResourcesInProgress(String projectName, String action) {
-        boolean edgeProgress = get(projectName).getEndpoints().stream().anyMatch(e ->
+	private void checkProjectRelatedResourcesInProgress(String projectName, List<ProjectEndpointDTO> endpoints, String action) {
+        boolean edgeProgress = endpoints.stream().anyMatch(e ->
                 Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING, UserInstanceStatus.STOPPING,
                         UserInstanceStatus.TERMINATING).contains(e.getStatus()));
 
-		List<UserInstanceDTO> userInstanceDTOs = exploratoryDAO.fetchProjectExploratoriesWhereStatusIn(projectName,
-				Arrays.asList(UserInstanceStatus.CREATING, UserInstanceStatus.STARTING,
-						UserInstanceStatus.CREATING_IMAGE, UserInstanceStatus.CONFIGURING,
-						UserInstanceStatus.RECONFIGURING, UserInstanceStatus.STOPPING, UserInstanceStatus.TERMINATING),
-                UserInstanceStatus.CREATING, UserInstanceStatus.CONFIGURING, UserInstanceStatus.STARTING,
-				UserInstanceStatus.RECONFIGURING, UserInstanceStatus.CREATING_IMAGE, UserInstanceStatus.STOPPING,
-				UserInstanceStatus.TERMINATING);
-        if (edgeProgress || !userInstanceDTOs.isEmpty()) {
+		List<String> endpointsName = endpoints.stream().map(ProjectEndpointDTO::getName).collect(Collectors.toList());
+		if (edgeProgress || !checkExploratoriesAndComputationalProgress(projectName, endpointsName)) {
 			throw new ResourceConflictException((String.format("Can not %s environment because one of project " +
 					"resource is in processing stage", action)));
 		}
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/endpoints/endpoints.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/management/endpoints/endpoints.component.ts
index 23eab1c..a5e5342 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/endpoints/endpoints.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/endpoints/endpoints.component.ts
@@ -22,9 +22,10 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms';
 import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
 import { ToastrService } from 'ngx-toastr';
 
-import { EndpointService } from '../../../core/services';
+import {EndpointService, UserResourceService} from '../../../core/services';
 import { NotificationDialogComponent } from '../../../shared/modal-dialog/notification-dialog';
 import { PATTERNS } from '../../../core/util';
+import {ExploratoryModel} from "../../../resources/resources-grid/resources-grid.model";
 
 export interface Endpoint {
   name: string;
@@ -41,6 +42,8 @@ export class EndpointsComponent implements OnInit {
   public createEndpointForm: FormGroup;
   endpoints: Endpoint[] = [];
   displayedColumns: string[] = ['name', 'url', 'account', 'endpoint_tag', 'actions'];
+  private resources: any;
+  private filtredResource: Array<any>;
 
   constructor(
     @Inject(MAT_DIALOG_DATA) public data: any,
@@ -48,12 +51,15 @@ export class EndpointsComponent implements OnInit {
     public dialogRef: MatDialogRef<EndpointsComponent>,
     public dialog: MatDialog,
     private endpointService: EndpointService,
-    private _fb: FormBuilder
+    private _fb: FormBuilder,
+    private userResourceService: UserResourceService,
+
   ) { }
 
   ngOnInit() {
     this.initFormModel();
     this.getEndpointList();
+    this.getResource();
   }
 
   public generateEndpointTag($event) {
@@ -68,12 +74,19 @@ export class EndpointsComponent implements OnInit {
   }
 
   public deleteEndpoint(data) {
-    this.dialog.open(NotificationDialogComponent, { data: { type: 'confirmation', item: data }, panelClass: 'modal-sm' })
+    if(this.resources.length){
+      this.filtredResource = this.resources.filter(project => {
+        project.filtredExploratory =  project.exploratory.filter(resource => resource.endpoint === data.name && resource.status !== 'terminated');
+        return project.filtredExploratory.length
+      });
+    }else{
+      this.filtredResource = this.resources
+    }
+
+    this.dialog.open(NotificationDialogComponent, { data: { type: 'confirmation', item: data, list: this.filtredResource }, panelClass: 'modal-sm' })
       .afterClosed().subscribe(result => {
-        result && this.endpointService.deleteEndpoint(data.name).subscribe(() => {
-          this.toastr.success('Endpoint successfully deleted!', 'Success!');
-          this.getEndpointList();
-        }, error => this.toastr.error(error.message || 'Endpoint creation failed!', 'Oops!'));
+        result === 'noTerminate' && this.deleteEndpointOption(data, false);
+        result === 'terminate' && this.deleteEndpointOption(data, true);
       });
   }
 
@@ -86,7 +99,21 @@ export class EndpointsComponent implements OnInit {
     });
   }
 
+  private deleteEndpointOption(data, option){
+    this.endpointService.deleteEndpoint(`${data.name}?with-resources=${option}`).subscribe(() => {
+      this.toastr.success(option ? 'Endpoint successfully disconnected. All related resources are terminated!' : 'Endpoint successfully disconnected!' , 'Success!');
+      this.getEndpointList();
+    }, error => this.toastr.error(error.message || 'Endpoint creation failed!', 'Oops!'));
+  }
+
   private getEndpointList() {
     this.endpointService.getEndpointsData().subscribe((endpoints: any) => this.endpoints = endpoints);
   }
+
+  private getResource(): void{
+  this.userResourceService.getUserProvisionedResources()
+   .subscribe((result: any) => {
+     this.resources = ExploratoryModel.loadEnvironments(result);
+    })
+  }
 }
diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts
index caf7245..8ad8eee 100644
--- a/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/administration/management/manage-environment/manage-environment-dilog.component.ts
@@ -115,11 +115,11 @@ export class ManageEnvironmentComponent implements OnInit {
     <button type="button" class="close" (click)="dialogRef.close()">&times;</button>
   </div>
   <div mat-dialog-content class="content">
-    <p>Environment of <b>{{ data.project }}</b> will be
+    <p>Environment of <span class="strong">{{ data.project }}</span> will be
       <span *ngIf="data.action === 'terminate'"> terminated.</span>
       <span *ngIf="data.action === 'stop'">stopped.</span>
     </p>
-    <p class="m-top-20"><strong>Do you want to proceed?</strong></p>
+    <p class="m-top-20"><span class="strong">Do you want to proceed?</span></p>
   </div>
   <div class="text-center">
     <button type="button" class="butt" mat-raised-button (click)="dialogRef.close()">No</button>
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 a501ccc..1804b34 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
@@ -105,7 +105,7 @@ export class ProjectComponent implements OnInit, OnDestroy {
   }
 
   public deleteProject($event) {
-    this.dialog.open(NotificationDialogComponent, { data: { type: 'confirmation', item: $event }, panelClass: 'modal-sm' })
+    this.dialog.open(NotificationDialogComponent, { data: { type: 'confirmation', item: $event, list: [] }, panelClass: 'modal-sm' })
       .afterClosed().subscribe(result => {
         result && this.projectService.deleteProject($event.name).subscribe(() => {
           this.refreshGrid();
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 7246ce4..ed6fc57 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
@@ -98,7 +98,6 @@ export class ResourcesGridComponent implements OnInit {
         this.environments = ExploratoryModel.loadEnvironments(result);
         this.getDefaultFilterConfiguration();
         (this.environments.length) ? this.getUserPreferences() : this.filteredEnvironments = [];
-
         this.healthStatus && !this.healthStatus.billingEnabled && this.modifyGrid();
       });
   }
diff --git a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/index.ts b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/index.ts
index 7f66ebc..501f2c6 100644
--- a/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/index.ts
+++ b/services/self-service/src/main/resources/webapp/src/app/shared/modal-dialog/notification-dialog/index.ts
@@ -22,11 +22,12 @@ import { CommonModule } from '@angular/common';
 
 import { NotificationDialogComponent } from './notification-dialog.component';
 import { MaterialModule } from '../../material.module';
+import {FormsModule} from "@angular/forms";
 
 export * from './notification-dialog.component';
 
 @NgModule({
-  imports: [CommonModule, MaterialModule],
+  imports: [CommonModule, MaterialModule, FormsModule],
   declarations: [NotificationDialogComponent],
   entryComponents: [NotificationDialogComponent],
   exports: [NotificationDialogComponent]
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 5664583..834792c 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
@@ -23,62 +23,95 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
 @Component({
   selector: 'notification-dialog',
   template: `
-  <div id="dialog-box">
-    <header class="dialog-header">
-      <h4 class="modal-title"><i class="material-icons">priority_high</i>Warning</h4>
-      <button type="button" class="close" (click)="dialogRef.close()">&times;</button>
-    </header>
-    <div mat-dialog-content class="content message">
-      <div *ngIf="data.type === 'list'" class="info">
-        <div *ngIf="data.template.notebook.length > 0">
-          Following notebook server<span *ngIf="data.template.notebook.length>1">s </span>
-          <span *ngFor="let item of data.template.notebook">
+      <div id="dialog-box">
+          <header class="dialog-header">
+              <h4 class="modal-title"><i class="material-icons">priority_high</i>Warning</h4>
+              <button type="button" class="close" (click)="dialogRef.close()">&times;</button>
+          </header>
+          <div mat-dialog-content class="content message">
+              <div *ngIf="data.type === 'list'" class="info">
+                  <div *ngIf="data.template.notebook.length > 0">
+                      Following notebook server<span *ngIf="data.template.notebook.length>1">s </span>
+                      <span *ngFor="let item of data.template.notebook">
             <span class="strong">{{ item.exploratory_name }}</span>
             <span *ngIf="data.template.notebook.length > 1">, </span>
           </span> will be stopped and all computational resources will be stopped/terminated
-        </div>
+                  </div>
 
-        <div *ngIf="data.template.cluster.length > 0">
-          <p *ngFor="let item of data.template.cluster">
-              Computational resource<span *ngIf="data.template.cluster.length > 1">s </span>
-              <span class="strong">{{ item.computational_name }}</span> on <span class="strong">{{ item.exploratory_name }}</span>
-              will be stopped
-          </p>
-        </div>
-        <span class="strong">by a schedule in 15 minutes.</span>
-      </div>
-      <div *ngIf="data.type === 'message'"><span [innerHTML]="data.template"></span></div>
-      <div *ngIf="data.type === 'confirmation'" class="confirm-dialog">          
-        <p *ngIf="data.template; else label">
-          <span [innerHTML]="data.template"></span>
-        </p>
-        <ng-template #label>
-          <p>
-            <span class="ellipsis label-name strong" matTooltip="{{ data.item.name }}" matTooltipPosition="above" [matTooltipDisabled]="data.item.name.length > 35">
-            {{ data.item.name }}</span> will be {{ data.action || 'decommissioned' }}.
-          </p>
-        </ng-template>
-        <mat-list *ngIf="data.item.endpoints?.length">
-            <mat-list-item class="list-header sans">
-                <div class="endpoint">Edge node in endpoint</div>
-                <div class="status">Further status</div>
-            </mat-list-item>
-            <div class="scrolling-content">
-                <mat-list-item *ngFor="let endpoint of data.item.endpoints" class="sans node">
-                    <div class="endpoint ellipsis">{{endpoint.name}}</div>
-                    <div class="status terminated">Terminated</div>
-                </mat-list-item>
-            </div>
-        </mat-list>          
-        <p class="m-top-20"><span class="strong">Do you want to proceed?</span></p>
-          
-        <div class="text-center m-top-30 m-bott-10">
-          <button type="button" class="butt" mat-raised-button (click)="dialogRef.close()">No</button>
-          <button type="button" class="butt butt-success" mat-raised-button (click)="dialogRef.close(true)">Yes</button>
-        </div>
+                  <div *ngIf="data.template.cluster.length > 0">
+                      <p *ngFor="let item of data.template.cluster">
+                          Computational resource<span *ngIf="data.template.cluster.length > 1">s </span>
+                          <span class="strong">{{ item.computational_name }}</span> on <span
+                              class="strong">{{ item.exploratory_name }}</span>
+                          will be stopped
+                      </p>
+                  </div>
+                  <span class="strong">by a schedule in 15 minutes.</span>
+              </div>
+              <div *ngIf="data.type === 'message'"><span [innerHTML]="data.template"></span></div>
+              <div *ngIf="data.type === 'confirmation'" class="confirm-dialog">
+                  <p *ngIf="data.template; else label">
+                      <span [innerHTML]="data.template"></span>
+                  </p>
+                  <ng-template #label>
+                      <p>
+            <span class="ellipsis label-name strong" matTooltip="{{ data.item.name }}" matTooltipPosition="above"
+                  [matTooltipDisabled]="data.item.name.length > 35">
+             {{ data.item.name }}</span> will be {{ data.action || 'disconnected' }}.
+                      </p>
+                  </ng-template>
+
+                  <div *ngIf="data.list && data.list.length && data.type === 'confirmation'">
+                      <div class="resource-list">
+                          <div class="resource-list-header">
+                              <div class="resource-name">Resource</div>
+                              <div class="project">Project</div>
+                          </div>
+                          <div class="scrolling-content resource-heigth">
+                              <div class="resource-list-row sans node" *ngFor="let project of data.list">
+                                  <div class="resource-name ellipsis">
+                                      <div *ngFor="let notebook of project.filtredExploratory">{{notebook.name}}</div>
+                                  </div>
+                                  <div class="project ellipsis">{{project.project}}</div>
+                              </div>
+                          </div>
+                      </div>
+                      <div class="confirm-resource-terminating">
+                          <label>
+                              <input class="checkbox" type="checkbox"
+                                     (change)="terminateResource()"/>Do not terminate all related resources
+                          </label>
+                      </div>
+                      <p class="confirm-message">
+                          <span *ngIf="!willNotTerminate">All connected computational resources will be terminated as well</span>
+                      </p>
+                  </div>
+                  <mat-list *ngIf="data.item.endpoints?.length">
+                      <mat-list-item class="list-header sans">
+                          <div class="endpoint">Edge node in endpoint</div>
+                          <div class="status">Further status</div>
+                      </mat-list-item>
+                      <div class="scrolling-content">
+                          <mat-list-item *ngFor="let endpoint of data.item.endpoints" class="sans node">
+                              <div class="endpoint ellipsis">{{endpoint.name}}</div>
+                              <div class="status terminated">Terminated</div>
+                          </mat-list-item>
+                      </div>
+                  </mat-list>
+                  <p class="m-top-20"><span class="strong">Do you want to proceed?</span></p>
+
+                  <div class="text-center m-top-30 m-bott-10">
+                      <button type="button" class="butt" mat-raised-button (click)="dialogRef.close()">No</button>
+                      <button *ngIf="!this.willNotTerminate" type="button" class="butt butt-success" mat-raised-button
+                              (click)="dialogRef.close('terminate')">Yes
+                      </button>
+                      <button *ngIf="this.willNotTerminate" type="button" class="butt butt-success" mat-raised-button
+                              (click)="dialogRef.close('noTerminate')">Yes
+                      </button>
+                  </div>
+              </div>
+          </div>
       </div>
-    </div>
-  </div>
   `,
   styles: [`
     .content { color: #718ba6; padding: 20px 50px; font-size: 14px; font-weight: 400; margin: 0; }
@@ -92,20 +125,32 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
     .label-name { display: inline-block; width: 100% } 
     .scrolling-content{overflow-y: auto; max-height: 200px; }
     .endpoint { width: 70%; text-align: left; color: #577289;}
-    .status { width: 30%;text-align: right;}
+    .status { width: 30%;text-align: left;}
     .label { font-size: 15px; font-weight: 500; font-family: "Open Sans",sans-serif;}
-    .node { font-weight: 300;}
-    .label-name { display: inline-block; width: 100%}
-    .scrolling-content{overflow-y: auto; max-height: 200px;}
-    .endpoint { width: 280px;text-align: left;}
-    .status { text-align: left;}
+    .node { font-weight: 300;}    
+    .label-name { display: inline-block; width: 100%}    
+    .resource-name { width: 280px;text-align: left; padding: 10px 0;line-height: 26px;}
+    .project { width: 30%;text-align: left; padding: 10px 0;line-height: 26px;}    
+    .resource-list{max-width: 100%; margin: 0 auto;margin-top: 20px; }
+    .resource-list-header{display: flex; font-weight: 600; font-size: 16px;height: 48px; border-top: 1px solid #edf1f5; border-bottom: 1px solid #edf1f5; padding: 0 20px;}
+    .resource-list-row{display: flex; border-bottom: 1px solid #edf1f5;padding: 0 20px;}
+    .confirm-resource-terminating{text-align: left; padding: 10px 20px;}
+    .confirm-message{color: #35afd5;font-size: 13px;min-height: 18px; text-align: center;}
+    .checkbox{margin-right: 5px;vertical-align: middle; margin-bottom: 3px;}
+    label{cursor: pointer}
+    
+    
   `]
 })
 export class NotificationDialogComponent {
+  public willNotTerminate: boolean = false;
   constructor(
     public dialogRef: MatDialogRef<NotificationDialogComponent>,
     @Inject(MAT_DIALOG_DATA) public data: any
   ) {
-    console.log(data);
+  }
+
+  public terminateResource(): void{
+    this.willNotTerminate = !this.willNotTerminate;
   }
 }


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