From 1c5c92147f7886ec4c7cd1a2348815d1198876d7 Mon Sep 17 00:00:00 2001
From: Oleksii Kurinnyi <okurinny@redhat.com>
Date: Tue, 29 Oct 2019 17:24:07 +0200
Subject: [PATCH] Disable autosave on Devfile editor page (#14913)

* Remove autosave on Devfile editor page

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* update ConfirmDialogService

Dialog window can have now only one button.

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>

* fix applying changes to running workspace

Signed-off-by: Oleksii Kurinnyi <okurinny@redhat.com>
---
 .../user-management.controller.ts             |   4 +-
 .../docker-registry-list.controller.ts        |   2 +-
 .../factory-information.controller.ts         |   4 +-
 .../list-factories.controller.ts              |   2 +-
 .../recent-workspaces.controller.ts           |  66 ++-
 .../list-organizations.controller.ts          |   2 +-
 .../organizations-item.controller.ts          |   2 +-
 .../organization-details.controller.ts        |   2 +-
 .../list-organization-members.controller.ts   |   4 +-
 .../app/teams/list/list-teams.controller.ts   |   2 +-
 .../list/team-item/team-item.controller.ts    |   2 +-
 .../team-details/team-details.controller.ts   |   4 +-
 .../list-team-members.controller.ts           |   2 +-
 .../member-item/member-item.controller.ts     |   2 +-
 .../create-workspace.service.ts               |  10 +-
 .../list-workspaces.controller.ts             |   2 +-
 .../share-workspace.controller.ts             |   2 +-
 .../user-item/user-item.controller.ts         |   2 +-
 .../workspace-devfile-editor.controller.ts    | 102 ++---
 .../list-env-variables.controller.ts          |   2 +-
 .../list-servers/list-servers.controller.ts   |   2 +-
 .../machine-config.controller.ts              |   2 +-
 .../list-commands/list-commands.controller.ts |   2 +-
 .../workspace-details.controller.ts           | 397 ++++++++----------
 .../workspace-details.directive.spec.ts       | 133 ++++--
 .../workspace-details/workspace-details.html  |  56 +--
 .../workspace-details.service.ts              | 137 ++++--
 .../env-variables.controller.ts               |   4 +-
 .../machine-servers.controller.ts             |   4 +-
 .../machine-volumes.controller.ts             |   4 +-
 .../workspace-machines.controller.ts          |   4 +-
 .../workspace-details-overview.controller.ts  |   2 +-
 .../workspace-details-overview.html           |   2 +-
 .../workspace-details-projects.controller.ts  |   2 +-
 .../confirm-dialog/che-confirm-dialog.html    |   5 +-
 .../confirm-dialog/confirm-dialog.service.ts  |   9 +-
 .../che-edit-mode-overlay.controller.ts       |  78 ++++
 .../che-edit-mode-overlay.directive.ts        |  80 +++-
 .../src/components/widget/widget-config.ts    |   2 +
 39 files changed, 707 insertions(+), 438 deletions(-)
 create mode 100644 dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.controller.ts

diff --git a/dashboard/src/app/admin/user-management/user-management.controller.ts b/dashboard/src/app/admin/user-management/user-management.controller.ts
index 7e890d7e83..c36ab47274 100644
--- a/dashboard/src/app/admin/user-management/user-management.controller.ts
+++ b/dashboard/src/app/admin/user-management/user-management.controller.ts
@@ -144,7 +144,7 @@ export class AdminsUserManagementCtrl {
    */
   removeUser(event: MouseEvent, user: any): void {
     let content = 'Are you sure you want to remove \'' + user.email + '\'?';
-    let promise = this.confirmDialogService.showConfirmDialog('Remove user', content, 'Delete', 'Cancel');
+    let promise = this.confirmDialogService.showConfirmDialog('Remove user', content, { resolve: 'Delete', reject: 'Cancel' });
 
     promise.then(() => {
       this.isLoading = true;
@@ -231,7 +231,7 @@ export class AdminsUserManagementCtrl {
       content += 'user?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove users', content, 'Delete', 'Cancel');
+    return this.confirmDialogService.showConfirmDialog('Remove users', content, { resolve: 'Delete', reject: 'Cancel' });
   }
 
   /**
diff --git a/dashboard/src/app/administration/docker-registry/docker-registry-list/docker-registry-list.controller.ts b/dashboard/src/app/administration/docker-registry/docker-registry-list/docker-registry-list.controller.ts
index 4b5ce8b87f..1d12298ddb 100644
--- a/dashboard/src/app/administration/docker-registry/docker-registry-list/docker-registry-list.controller.ts
+++ b/dashboard/src/app/administration/docker-registry/docker-registry-list/docker-registry-list.controller.ts
@@ -163,7 +163,7 @@ export class DockerRegistryListController {
     } else {
       content += 'this selected registry?';
     }
-    return this.confirmDialogService.showConfirmDialog('Remove registries', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove registries', content, { resolve: 'Delete' });
   }
 
   /**
diff --git a/dashboard/src/app/factories/factory-details/information-tab/factory-information/factory-information.controller.ts b/dashboard/src/app/factories/factory-details/information-tab/factory-information/factory-information.controller.ts
index 11bb04ef57..aa5f64dfc3 100644
--- a/dashboard/src/app/factories/factory-details/information-tab/factory-information/factory-information.controller.ts
+++ b/dashboard/src/app/factories/factory-details/information-tab/factory-information/factory-information.controller.ts
@@ -218,7 +218,7 @@ export class FactoryInformationController {
 
     const title = 'Warning',
       content = `You have unsaved changes in JSON configuration. Would you like to save changes now?`;
-    return this.confirmDialogService.showConfirmDialog(title, content, 'Continue').then(() => {
+    return this.confirmDialogService.showConfirmDialog(title, content, { resolve: 'Continue' }).then(() => {
       this.updateFactoryContent();
     });
   }
@@ -296,7 +296,7 @@ export class FactoryInformationController {
    */
   deleteFactory(): void {
     let content = 'Please confirm removal for the factory \'' + (this.factory.name ? this.factory.name : this.factory.id) + '\'.';
-    let promise = this.confirmDialogService.showConfirmDialog('Remove the factory', content, 'Delete');
+    let promise = this.confirmDialogService.showConfirmDialog('Remove the factory', content, { resolve: 'Delete' });
 
     promise.then(() => {
       // remove it !
diff --git a/dashboard/src/app/factories/list-factories/list-factories.controller.ts b/dashboard/src/app/factories/list-factories/list-factories.controller.ts
index 94e5438911..bd46e06a8d 100644
--- a/dashboard/src/app/factories/list-factories/list-factories.controller.ts
+++ b/dashboard/src/app/factories/list-factories/list-factories.controller.ts
@@ -227,6 +227,6 @@ export class ListFactoriesController {
     } else {
       content += 'this selected factory?';
     }
-    return this.confirmDialogService.showConfirmDialog('Remove factories', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove factories', content, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/navbar/recent-workspaces/recent-workspaces.controller.ts b/dashboard/src/app/navbar/recent-workspaces/recent-workspaces.controller.ts
index f62468a381..b74119717e 100644
--- a/dashboard/src/app/navbar/recent-workspaces/recent-workspaces.controller.ts
+++ b/dashboard/src/app/navbar/recent-workspaces/recent-workspaces.controller.ts
@@ -10,11 +10,12 @@
  *   Red Hat, Inc. - initial API and implementation
  */
 'use strict';
-import {CheWorkspace} from '../../../components/api/workspace/che-workspace.factory';
+import { CheWorkspace } from '../../../components/api/workspace/che-workspace.factory';
 import IdeSvc from '../../../app/ide/ide.service';
-import {CheBranding} from '../../../components/branding/che-branding.factory';
-import {WorkspacesService} from '../../workspaces/workspaces.service';
-import {CheNotification} from '../../../components/notification/che-notification.factory';
+import { CheBranding } from '../../../components/branding/che-branding.factory';
+import { WorkspacesService } from '../../workspaces/workspaces.service';
+import { CheNotification } from '../../../components/notification/che-notification.factory';
+import { WorkspaceDetailsService } from '../../workspaces/workspace-details/workspace-details.service';
 
 
 const MAX_RECENT_WORKSPACES_ITEMS: number = 5;
@@ -27,7 +28,18 @@ const MAX_RECENT_WORKSPACES_ITEMS: number = 5;
  */
 export class NavbarRecentWorkspacesController {
 
-  static $inject = ['ideSvc', 'cheWorkspace', 'cheBranding', '$window', '$log', '$scope', '$rootScope', 'workspacesService', 'cheNotification'];
+  static $inject = [
+    'ideSvc',
+    'cheWorkspace',
+    'cheBranding',
+    '$window',
+    '$log',
+    '$scope',
+    '$rootScope',
+    'workspacesService',
+    'cheNotification',
+    'workspaceDetailsService'
+  ];
 
   cheWorkspace: CheWorkspace;
   dropdownItemTempl: Array<any>;
@@ -45,19 +57,23 @@ export class NavbarRecentWorkspacesController {
   workspacesService: WorkspacesService;
   cheNotification: CheNotification;
   cheBranding: CheBranding;
+  workspaceDetailsService: WorkspaceDetailsService;
 
   /**
    * Default constructor
    */
-  constructor(ideSvc: IdeSvc,
-              cheWorkspace: CheWorkspace,
-              cheBranding: CheBranding,
-              $window: ng.IWindowService,
-              $log: ng.ILogService,
-              $scope: ng.IScope,
-              $rootScope: ng.IRootScopeService,
-              workspacesService: WorkspacesService,
-              cheNotification: CheNotification) {
+  constructor(
+    ideSvc: IdeSvc,
+    cheWorkspace: CheWorkspace,
+    cheBranding: CheBranding,
+    $window: ng.IWindowService,
+    $log: ng.ILogService,
+    $scope: ng.IScope,
+    $rootScope: ng.IRootScopeService,
+    workspacesService: WorkspacesService,
+    cheNotification: CheNotification,
+    workspaceDetailsService: WorkspaceDetailsService
+  ) {
     this.ideSvc = ideSvc;
     this.cheWorkspace = cheWorkspace;
     this.$log = $log;
@@ -66,6 +82,7 @@ export class NavbarRecentWorkspacesController {
     this.workspacesService = workspacesService;
     this.cheNotification = cheNotification;
     this.cheBranding = cheBranding;
+    this.workspaceDetailsService = workspaceDetailsService;
 
     // workspace updated time map by id
     this.workspaceUpdated = new Map();
@@ -320,6 +337,11 @@ export class NavbarRecentWorkspacesController {
    * @param workspaceId {String} workspace id
    */
   stopRecentWorkspace(workspaceId: string): void {
+    if (this.checkUnsavedChanges(workspaceId)) {
+      this.workspaceDetailsService.notifyUnsavedChangesDialog();
+      return;
+    }
+
     this.cheWorkspace.stopWorkspace(workspaceId).then(() => {
       angular.noop();
     }, (error: any) => {
@@ -333,11 +355,16 @@ export class NavbarRecentWorkspacesController {
    * @param workspaceId {String} workspace id
    */
   runRecentWorkspace(workspaceId: string): void {
+    if (this.checkUnsavedChanges(workspaceId)) {
+      this.workspaceDetailsService.notifyUnsavedChangesDialog();
+      return;
+    }
+
     let workspace = this.cheWorkspace.getWorkspaceById(workspaceId);
 
     this.updateRecentWorkspace(workspaceId);
 
-    this.cheWorkspace.startWorkspace(workspace.id, workspace.config ? workspace.config.defaultEnv: null).catch((error: any) => {
+    this.cheWorkspace.startWorkspace(workspace.id, workspace.config ? workspace.config.defaultEnv : null).catch((error: any) => {
       this.$log.error(error);
       this.cheNotification.showError('Run workspace error.', error);
     });
@@ -352,4 +379,13 @@ export class NavbarRecentWorkspacesController {
   updateRecentWorkspace(workspaceId: string): void {
     this.$rootScope.$broadcast('recent-workspace:set', workspaceId);
   }
+
+  /**
+   * Returns `true` if workspace configuration has unsaved changes.
+   * @param id a workspace ID
+   */
+  checkUnsavedChanges(id: string): ng.IPromise<any> | any {
+    return this.workspaceDetailsService.isWorkspaceConfigSaved(id) === false;
+  }
+
 }
diff --git a/dashboard/src/app/organizations/list-organizations/list-organizations.controller.ts b/dashboard/src/app/organizations/list-organizations/list-organizations.controller.ts
index 6c68604e51..d94e72b8a0 100644
--- a/dashboard/src/app/organizations/list-organizations/list-organizations.controller.ts
+++ b/dashboard/src/app/organizations/list-organizations/list-organizations.controller.ts
@@ -338,7 +338,7 @@ export class ListOrganizationsController {
       content += 'this selected organization?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Delete organizations', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Delete organizations', content, { resolve: 'Delete' });
   }
 
 }
diff --git a/dashboard/src/app/organizations/list-organizations/organizations-item/organizations-item.controller.ts b/dashboard/src/app/organizations/list-organizations/organizations-item/organizations-item.controller.ts
index f64ec5f93f..b395b1cb86 100644
--- a/dashboard/src/app/organizations/list-organizations/organizations-item/organizations-item.controller.ts
+++ b/dashboard/src/app/organizations/list-organizations/organizations-item/organizations-item.controller.ts
@@ -140,6 +140,6 @@ export class OrganizationsItemController {
    */
   confirmRemoval(): ng.IPromise<any> {
     return this.confirmDialogService.showConfirmDialog('Delete organization',
-      'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
+      'Would you like to delete organization \'' + this.organization.name + '\'?', { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/organizations/organization-details/organization-details.controller.ts b/dashboard/src/app/organizations/organization-details/organization-details.controller.ts
index 29ec249ea7..a490687864 100644
--- a/dashboard/src/app/organizations/organization-details/organization-details.controller.ts
+++ b/dashboard/src/app/organizations/organization-details/organization-details.controller.ts
@@ -406,7 +406,7 @@ export class OrganizationDetailsController {
    */
   deleteOrganization(): void {
     let promise = this.confirmDialogService.showConfirmDialog('Delete organization',
-      'Would you like to delete organization \'' + this.organization.name + '\'?', 'Delete');
+      'Would you like to delete organization \'' + this.organization.name + '\'?', { resolve: 'Delete' });
 
     promise.then(() => {
       let promise = this.cheOrganization.deleteOrganization(this.organization.id);
diff --git a/dashboard/src/app/organizations/organization-details/organization-members/list-organization-members.controller.ts b/dashboard/src/app/organizations/organization-details/organization-members/list-organization-members.controller.ts
index 8f801ddf8c..707fdc2291 100644
--- a/dashboard/src/app/organizations/organization-details/organization-members/list-organization-members.controller.ts
+++ b/dashboard/src/app/organizations/organization-details/organization-members/list-organization-members.controller.ts
@@ -404,7 +404,7 @@ export class ListOrganizationMembersController {
    * @param member
    */
   removeMember(member: che.IMember): void {
-    let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member  ' + member.email + ' ?', 'Delete');
+    let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member  ' + member.email + ' ?', { resolve: 'Delete' });
 
     promise.then(() => {
       this.removePermissions(member);
@@ -507,6 +507,6 @@ export class ListOrganizationMembersController {
       confirmTitle += 'the selected member?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/teams/list/list-teams.controller.ts b/dashboard/src/app/teams/list/list-teams.controller.ts
index dbf2599fc5..12a65545bf 100644
--- a/dashboard/src/app/teams/list/list-teams.controller.ts
+++ b/dashboard/src/app/teams/list/list-teams.controller.ts
@@ -359,6 +359,6 @@ export class ListTeamsController {
       content += 'this selected team?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Delete teams', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Delete teams', content, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/teams/list/team-item/team-item.controller.ts b/dashboard/src/app/teams/list/team-item/team-item.controller.ts
index 3265be0101..7ff9460ed6 100644
--- a/dashboard/src/app/teams/list/team-item/team-item.controller.ts
+++ b/dashboard/src/app/teams/list/team-item/team-item.controller.ts
@@ -94,7 +94,7 @@ export class TeamItemController {
    */
   confirmRemoval(): ng.IPromise<any> {
     let promise = this.confirmDialogService.showConfirmDialog('Delete team',
-      'Would you like to delete team \'' + this.team.name + '\'?', 'Delete');
+      'Would you like to delete team \'' + this.team.name + '\'?', { resolve: 'Delete' });
     return promise;
   }
 }
diff --git a/dashboard/src/app/teams/team-details/team-details.controller.ts b/dashboard/src/app/teams/team-details/team-details.controller.ts
index 529f20e987..90573ead65 100644
--- a/dashboard/src/app/teams/team-details/team-details.controller.ts
+++ b/dashboard/src/app/teams/team-details/team-details.controller.ts
@@ -315,7 +315,7 @@ tab: Object = Tab;
    */
   deleteTeam(event: MouseEvent): void {
     let promise = this.confirmDialogService.showConfirmDialog('Delete team',
-      'Would you like to delete team \'' + this.team.name + '\'?', 'Delete');
+      'Would you like to delete team \'' + this.team.name + '\'?', { resolve: 'Delete' });
 
     promise.then(() => {
       let promise = this.cheTeam.deleteTeam(this.team.id);
@@ -334,7 +334,7 @@ tab: Object = Tab;
    */
   leaveTeam(): void {
     let promise = this.confirmDialogService.showConfirmDialog('Leave team',
-      'Would you like to leave team \'' + this.team.name + '\'?', 'Leave');
+      'Would you like to leave team \'' + this.team.name + '\'?', { resolve: 'Leave' });
 
     promise.then(() => {
       let promise = this.chePermissions.removeOrganizationPermissions(this.team.id, this.cheUser.getUser().id);
diff --git a/dashboard/src/app/teams/team-details/team-members/list-team-members.controller.ts b/dashboard/src/app/teams/team-details/team-members/list-team-members.controller.ts
index 0a083209f7..09841309d3 100644
--- a/dashboard/src/app/teams/team-details/team-members/list-team-members.controller.ts
+++ b/dashboard/src/app/teams/team-details/team-members/list-team-members.controller.ts
@@ -554,6 +554,6 @@ export class ListTeamMembersController {
       confirmTitle += 'the selected member?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove members', confirmTitle, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/teams/team-details/team-members/member-item/member-item.controller.ts b/dashboard/src/app/teams/team-details/team-members/member-item/member-item.controller.ts
index a26d839394..fe0ce50aeb 100644
--- a/dashboard/src/app/teams/team-details/team-members/member-item/member-item.controller.ts
+++ b/dashboard/src/app/teams/team-details/team-members/member-item/member-item.controller.ts
@@ -73,7 +73,7 @@ export class MemberItemController {
    * @param  event - the $event
    */
   removeMember(event: MouseEvent): void {
-    let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member  ' + this.member.email + ' ?', 'Delete');
+    let promise = this.confirmDialogService.showConfirmDialog('Remove member', 'Would you like to remove member  ' + this.member.email + ' ?', { resolve: 'Delete' });
 
     promise.then(() => {
       if (this.member.isPending) {
diff --git a/dashboard/src/app/workspaces/create-workspace/create-workspace.service.ts b/dashboard/src/app/workspaces/create-workspace/create-workspace.service.ts
index 8eedc46b6b..0ae9c14d28 100644
--- a/dashboard/src/app/workspaces/create-workspace/create-workspace.service.ts
+++ b/dashboard/src/app/workspaces/create-workspace/create-workspace.service.ts
@@ -197,8 +197,8 @@ export class CreateWorkspaceSvc {
       });
 
       projects.push(template);
-    });     
-    
+    });
+
     return this.checkEditingProgress().then(() => {
       sourceDevfile.projects = projects;
 
@@ -206,7 +206,7 @@ export class CreateWorkspaceSvc {
       if (noProjectsFromDevfile) {
         sourceDevfile.commands = [];
       }
-      
+
       return this.cheWorkspace.createWorkspaceFromDevfile(namespaceId, sourceDevfile, attributes).then((workspace: che.IWorkspace) => {
         return this.cheWorkspace.fetchWorkspaces().then(() => this.cheWorkspace.getWorkspaceById(workspace.id));
       })
@@ -246,7 +246,7 @@ export class CreateWorkspaceSvc {
 
     const title = 'Warning',
           content = `You have project editing, that is not completed. Would you like to proceed to workspace creation without these changes?`;
-    return this.confirmDialogService.showConfirmDialog(title, content, 'Continue');
+    return this.confirmDialogService.showConfirmDialog(title, content, { resolve: 'Continue' });
   }
 
   /**
@@ -289,7 +289,7 @@ export class CreateWorkspaceSvc {
 
   /**
    * Returns name of the pointed workspace.
-   * 
+   *
    * @param workspace workspace
    */
   getWorkspaceName(workspace: che.IWorkspace): string {
diff --git a/dashboard/src/app/workspaces/list-workspaces/list-workspaces.controller.ts b/dashboard/src/app/workspaces/list-workspaces/list-workspaces.controller.ts
index 432bb9ba4f..7b2ed20781 100644
--- a/dashboard/src/app/workspaces/list-workspaces/list-workspaces.controller.ts
+++ b/dashboard/src/app/workspaces/list-workspaces/list-workspaces.controller.ts
@@ -305,7 +305,7 @@ export class ListWorkspacesCtrl {
       content += 'this selected workspace?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove workspaces', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove workspaces', content, { resolve: 'Delete' });
   }
 
   /**
diff --git a/dashboard/src/app/workspaces/share-workspace/share-workspace.controller.ts b/dashboard/src/app/workspaces/share-workspace/share-workspace.controller.ts
index 7297a4ce12..742bccfab0 100644
--- a/dashboard/src/app/workspaces/share-workspace/share-workspace.controller.ts
+++ b/dashboard/src/app/workspaces/share-workspace/share-workspace.controller.ts
@@ -344,7 +344,7 @@ export class ShareWorkspaceController {
       content += 'this selected member?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove members', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove members', content, { resolve: 'Delete' });
   }
 
   /**
diff --git a/dashboard/src/app/workspaces/share-workspace/user-item/user-item.controller.ts b/dashboard/src/app/workspaces/share-workspace/user-item/user-item.controller.ts
index 8d74e4a834..bf7cc79ddd 100644
--- a/dashboard/src/app/workspaces/share-workspace/user-item/user-item.controller.ts
+++ b/dashboard/src/app/workspaces/share-workspace/user-item/user-item.controller.ts
@@ -43,7 +43,7 @@ export class UserItemController {
    */
   removeUser(): void {
     let content = 'Please confirm removal for the member \'' + this.user.email + '\'.';
-    let promise = this.confirmDialogService.showConfirmDialog('Remove the member', content, 'Delete');
+    let promise = this.confirmDialogService.showConfirmDialog('Remove the member', content, { resolve: 'Delete' });
 
     promise.then(() => {
       // callback is set in scope definition:
diff --git a/dashboard/src/app/workspaces/workspace-details/devfile/workspace-devfile-editor.controller.ts b/dashboard/src/app/workspaces/workspace-details/devfile/workspace-devfile-editor.controller.ts
index 81b922ca46..8f8ddd1e68 100644
--- a/dashboard/src/app/workspaces/workspace-details/devfile/workspace-devfile-editor.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/devfile/workspace-devfile-editor.controller.ts
@@ -18,28 +18,29 @@
  */
 export class WorkspaceDevfileEditorController {
 
-  static $inject = ['$log', '$scope', '$timeout'];
+  static $inject = [
+    '$log',
+    '$scope',
+    '$timeout'
+  ];
+  private $log: ng.ILogService;
+  private $scope: ng.IScope;
+  private $timeout: ng.ITimeoutService;
 
-  $log: ng.ILogService;
-  $scope: ng.IScope;
-  $timeout: ng.ITimeoutService;
+  private isActive: boolean;
+  private workspaceDevfile: che.IWorkspaceDevfile;
+  private workspaceDevfileOnChange: Function;
 
-  editorOptions: {
+  private editorOptions: {
     lineWrapping: boolean,
     lineNumbers: boolean,
     matchBrackets: boolean,
     mode: string,
     onLoad: Function
   };
-  devfileValidationMessages: string[] = [];
-  isActive: boolean;
-  workspaceDevfile: che.IWorkspaceDevfile;
-  devfileYaml: string;
-  newWorkspaceDevfile: che.IWorkspaceDevfile;
-  workspaceDevfileOnChange: Function;
+  private validationErrors: string[] = [];
+  private devfileYaml: string;
   private saveTimeoutPromise: ng.IPromise<any>;
-  private isSaving: boolean;
-
 
   /**
    * Default constructor that is using resource
@@ -48,70 +49,73 @@ export class WorkspaceDevfileEditorController {
     this.$log = $log;
     this.$scope = $scope;
     this.$timeout = $timeout;
-    this.isSaving = false;
-    this.devfileYaml = jsyaml.dump(this.workspaceDevfile);
+
+    this.$scope.$on('edit-workspace-details', (event: ng.IAngularEvent, attrs: { status: string }) => {
+      if (attrs.status === 'cancelled') {
+        this.$onInit();
+      }
+    });
 
     $scope.$watch(() => {
       return this.workspaceDevfile;
     }, () => {
-      let editedWorkspaceDevfile;
+      let devfile: che.IWorkspaceDevfile;
       try {
-        editedWorkspaceDevfile = jsyaml.load(this.devfileYaml);
-        angular.extend(editedWorkspaceDevfile, this.workspaceDevfile);
+        devfile = jsyaml.safeLoad(this.devfileYaml);
       } catch (e) {
-        editedWorkspaceDevfile = this.workspaceDevfile;
+        return;
+      }
+
+      if (angular.equals(devfile, this.workspaceDevfile) === false) {
+        angular.extend(devfile, this.workspaceDevfile);
+        this.devfileYaml = jsyaml.safeDump(devfile);
+        this.validate();
       }
-      this.devfileYaml = jsyaml.dump(this.workspaceDevfile);
-      const validateOnly = true;
-      this.onChange(validateOnly);
     }, true);
   }
 
-  $onInit(): void { }
+  $onInit(): void {
+    this.devfileYaml = jsyaml.safeDump(this.workspaceDevfile);
+  }
 
-  /**
-   * Callback when editor content is changed.
-   */
-  onChange(validateOnly?: boolean): void {
-    this.devfileValidationMessages = [];
-    if (!this.devfileYaml) {
-      return;
-    }
+  validate() {
+    this.validationErrors = [];
 
-    let devfile;
+    let devfile: che.IWorkspaceDevfile;
     try {
-      devfile = jsyaml.load(this.devfileYaml);
+      devfile = jsyaml.safeLoad(this.devfileYaml);
     } catch (e) {
       if (e.name === 'YAMLException') {
-        this.devfileValidationMessages = [e.message];
+        this.validationErrors = [e.message];
       }
-      if (this.devfileValidationMessages.length === 0) {
-        this.devfileValidationMessages = ['Devfile is invalid.'];
+      if (this.validationErrors.length === 0) {
+        this.validationErrors = ['Devfile is invalid.'];
       }
       this.$log.error(e);
     }
+  }
 
-    if (validateOnly || !this.isActive) {
+  /**
+   * Callback when editor content is changed.
+   */
+  onChange(): void {
+    if (!this.isActive) {
       return;
     }
-    this.isSaving = (this.devfileValidationMessages.length === 0) && !angular.equals(devfile, this.workspaceDevfile);
 
     if (this.saveTimeoutPromise) {
       this.$timeout.cancel(this.saveTimeoutPromise);
     }
 
     this.saveTimeoutPromise = this.$timeout(() => {
-      // immediately apply config on IU
-      this.newWorkspaceDevfile = angular.copy(devfile);
-      this.isSaving = false;
-      this.applyChanges();
-    }, 2000);
-  }
+      this.validate();
+      if (this.validationErrors.length !== 0) {
+        return;
+      }
 
-  /**
-   * Callback when user applies new config.
-   */
-  applyChanges(): void {
-    this.workspaceDevfileOnChange({devfile: this.newWorkspaceDevfile});
+      angular.extend(this.workspaceDevfile, jsyaml.safeLoad(this.devfileYaml));
+      this.workspaceDevfileOnChange();
+    }, 200);
   }
+
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.ts b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.ts
index 65fb662223..6ed3b56219 100644
--- a/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/environments/list-env-variables/list-env-variables.controller.ts
@@ -191,7 +191,7 @@ export class ListEnvVariablesController {
       content += 'this selected variable?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove variables', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove variables', content, { resolve: 'Delete' });
   }
 
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/list-servers/list-servers.controller.ts b/dashboard/src/app/workspaces/workspace-details/environments/list-servers/list-servers.controller.ts
index f808a00655..6ba25e405c 100644
--- a/dashboard/src/app/workspaces/workspace-details/environments/list-servers/list-servers.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/environments/list-servers/list-servers.controller.ts
@@ -213,7 +213,7 @@ export class ListServersController {
       content += 'this selected server?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove servers', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove servers', content, { resolve: 'Delete' });
   }
 
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.ts b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.ts
index 81fcdecd89..2ae57583b6 100644
--- a/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/environments/machine-config/machine-config.controller.ts
@@ -232,7 +232,7 @@ export class WorkspaceMachineConfigController {
   deleteMachine($event: MouseEvent): void {
     let promise;
     if (!this.machineConfig.isDev) {
-      promise = this.confirmDialogService.showConfirmDialog('Remove container', 'Would you like to delete this container?', 'Delete');
+      promise = this.confirmDialogService.showConfirmDialog('Remove container', 'Would you like to delete this container?', { resolve: 'Delete' });
     } else {
       promise = this.showDeleteDevMachineDialog($event);
     }
diff --git a/dashboard/src/app/workspaces/workspace-details/list-commands/list-commands.controller.ts b/dashboard/src/app/workspaces/workspace-details/list-commands/list-commands.controller.ts
index d9b280f930..84c3ed26b7 100644
--- a/dashboard/src/app/workspaces/workspace-details/list-commands/list-commands.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/list-commands/list-commands.controller.ts
@@ -205,6 +205,6 @@ export class ListCommandsController {
       content += 'this selected command?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove commands', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove commands', content, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.ts
index 5580f93f22..f7280ea8b9 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.controller.ts
@@ -13,10 +13,10 @@
 import {CheWorkspace, WorkspaceStatus} from '../../../components/api/workspace/che-workspace.factory';
 import {CheNotification} from '../../../components/notification/che-notification.factory';
 import {WorkspaceDetailsService} from './workspace-details.service';
-import IdeSvc from '../../ide/ide.service';
 import {WorkspacesService} from '../workspaces.service';
 import {ICheEditModeOverlayConfig} from '../../../components/widget/edit-mode-overlay/che-edit-mode-overlay.directive';
 import {CheBranding} from '../../../components/branding/che-branding.factory';
+import {WorkspaceDataManager} from '../../../components/api/workspace/workspace-data-manager';
 
 export  interface IInitData {
   namespaceId: string;
@@ -34,23 +34,33 @@ export  interface IInitData {
  */
 export class WorkspaceDetailsController {
 
-  static $inject = ['$location', '$log', '$sce', '$scope', 'lodash', 'cheNotification', 'cheWorkspace', 'ideSvc', 'workspaceDetailsService', 'initData', '$timeout', 'cheBranding', 'workspacesService'];
+  static $inject = [
+    '$location',
+    '$q',
+    '$sce',
+    '$scope',
+    '$timeout',
+    'cheNotification',
+    'cheWorkspace',
+    'workspaceDetailsService',
+    'initData',
+    'cheBranding',
+    'workspacesService'
+  ];
 
   /**
    * Overlay panel configuration.
    */
-  editOverlayConfig: ICheEditModeOverlayConfig;
-  workspaceDetails: che.IWorkspace;
-  workspacesService: WorkspacesService;
-  private lodash: any;
+  private editOverlayConfig: ICheEditModeOverlayConfig;
+  private workspaceDetails: che.IWorkspace;
+  private workspacesService: WorkspacesService;
+  private $q: ng.IQService;
   private $scope: ng.IScope;
   private $sce: ng.ISCEService;
-  private $log: ng.ILogService;
   private $location: ng.ILocationService;
   private $timeout: ng.ITimeoutService;
   private cheNotification: CheNotification;
   private cheWorkspace: CheWorkspace;
-  private ideSvc: IdeSvc;
   private workspaceDetailsService: WorkspaceDetailsService;
   private loading: boolean = false;
   private selectedTabIndex: number;
@@ -58,58 +68,55 @@ export class WorkspaceDetailsController {
   private workspaceId: string = '';
   private workspaceName: string = '';
   private newName: string = '';
-  private originWorkspaceDetails: any = {};
+  private initialWorkspaceDetails: che.IWorkspace = {};
   private workspaceImportedRecipe: che.IRecipe;
   private forms: Map<string, ng.IFormController> = new Map();
   private tab: { [key: string]: string } = {};
   private errorMessage: string = '';
+  private failedTabs: string[] = [];
   private tabsValidationTimeout: ng.IPromise<any>;
   private pluginRegistry: string;
   private TAB: Array<string>;
   private cheBranding: CheBranding;
+  private workspaceDataManager: WorkspaceDataManager;
 
   /**
-   * There are unsaved changes to apply (with restart) when is't <code>true</code>.
-   */
-  private unsavedChangesToApply: boolean;
-  /**
-   * There is selected deprecated editor when is't <code>true</code>.
+   * There is selected deprecated editor when it's <code>true</code>.
    */
   private hasSelectedDeprecatedEditor: boolean;
   /**
-   * There are selected deprecated plugins when is't <code>true</code>.
+   * There are selected deprecated plugins when it's <code>true</code>.
    */
   private hasSelectedDeprecatedPlugins: boolean;
 
   /**
    * Default constructor that is using resource injection
    */
-  constructor($location: ng.ILocationService,
-              $log: ng.ILogService,
-              $sce: ng.ISCEService,
-              $scope: ng.IScope,
-              lodash: any,
-              cheNotification: CheNotification,
-              cheWorkspace: CheWorkspace,
-              ideSvc: IdeSvc,
-              workspaceDetailsService: WorkspaceDetailsService,
-              initData: IInitData,
-              $timeout: ng.ITimeoutService,
-              cheBranding: CheBranding,
-              workspacesService: WorkspacesService) {
-    this.$log = $log;
+  constructor(
+    $location: ng.ILocationService,
+    $q: ng.IQService,
+    $sce: ng.ISCEService,
+    $scope: ng.IScope,
+    $timeout: ng.ITimeoutService,
+    cheNotification: CheNotification,
+    cheWorkspace: CheWorkspace,
+    workspaceDetailsService: WorkspaceDetailsService,
+    initData: IInitData,
+    cheBranding: CheBranding,
+    workspacesService: WorkspacesService
+  ) {
+    this.$location = $location;
+    this.$q = $q;
     this.$sce = $sce;
     this.$scope = $scope;
     this.$timeout = $timeout;
-    this.$location = $location;
-    this.lodash = lodash;
     this.cheNotification = cheNotification;
     this.cheWorkspace = cheWorkspace;
-    this.ideSvc = ideSvc;
     this.workspaceDetailsService = workspaceDetailsService;
     this.workspacesService = workspacesService;
     this.cheBranding = cheBranding;
     this.pluginRegistry = cheWorkspace.getWorkspaceSettings() != null ? cheWorkspace.getWorkspaceSettings().cheWorkspacePluginRegistryUrl : null;
+    this.workspaceDataManager = this.cheWorkspace.getWorkspaceDataManager();
 
     if (!initData.workspaceDetails) {
       cheNotification.showError(`There is no workspace with name ${initData.workspaceName}`);
@@ -122,22 +129,24 @@ export class WorkspaceDetailsController {
     this.workspaceId = initData.workspaceDetails.id;
 
     const action = (newWorkspaceDetails: che.IWorkspace) => {
-      if (angular.equals(newWorkspaceDetails, this.originWorkspaceDetails)) {
+      if (this.initialWorkspaceDetails.config && angular.equals(newWorkspaceDetails.config, this.initialWorkspaceDetails.config)) {
+        return;
+      } else if (this.initialWorkspaceDetails.devfile && angular.equals(newWorkspaceDetails.devfile, this.initialWorkspaceDetails.devfile)) {
         return;
       }
 
-      this.originWorkspaceDetails = angular.copy(newWorkspaceDetails);
-      if (this.unsavedChangesToApply === false) {
+      this.initialWorkspaceDetails = angular.copy(newWorkspaceDetails);
+      if (this.workspaceDetailsService.isWorkspaceModified(this.workspaceId)) {
         this.workspaceDetails = angular.copy(newWorkspaceDetails);
+        this.workspaceName = this.workspaceDataManager.getName(this.workspaceDetails);
       }
       this.checkEditMode();
       this.updateDeprecatedInfo();
     };
     this.cheWorkspace.subscribeOnWorkspaceChange(initData.workspaceDetails.id, action);
 
-    this.originWorkspaceDetails = angular.copy(initData.workspaceDetails);
+    this.initialWorkspaceDetails = angular.copy(initData.workspaceDetails);
     this.workspaceDetails = angular.copy(initData.workspaceDetails);
-    this.checkEditMode();
     this.updateDeprecatedInfo();
     this.TAB = this.workspaceDetails.config ? ['Overview', 'Projects', 'Containers', 'Servers', 'Env_Variables', 'Volumes', 'Config', 'SSH', 'Plugins', 'Editors'] : ['Overview', 'Projects', 'Plugins', 'Editors', 'Devfile'];
     this.updateTabs();
@@ -150,36 +159,48 @@ export class WorkspaceDetailsController {
         this.updateSelectedTab(tab);
       }
     }, true);
+    const failedTabsDeregistrationFn = $scope.$watch(() => {
+      return this.checkForFailedTabs();
+    }, () => {
+      const isSaved = this.workspaceDetailsService.isWorkspaceConfigSaved(this.workspaceId);
+      this.updateEditModeOverlayConfig(isSaved === false);
+    }, true);
     $scope.$on('$destroy', () => {
       this.cheWorkspace.unsubscribeOnWorkspaceChange(this.workspaceId, action);
       searchDeRegistrationFn();
+      failedTabsDeregistrationFn();
     });
 
     this.editOverlayConfig = {
       visible: false,
       disabled: false,
       message: {
-        content: this.getOverlayMessage(),
+        content: '',
         visible: false
       },
       applyButton: {
         action: () => {
           this.applyConfigChanges();
         },
-        disabled: this.workspaceDetailsService.getRestartToApply(this.workspaceId) === false,
+        disabled: true,
         title: 'Apply'
       },
       saveButton: {
         action: () => {
-          this.saveConfigChanges(true);
+          this.saveConfigChanges();
         },
         title: 'Save',
-        disabled: false
+        disabled: true
       },
       cancelButton: {
         action: () => {
           this.cancelConfigChanges();
         }
+      },
+      preventPageLeave: false,
+      onChangesDiscard: (): ng.IPromise<void> => {
+        this.cancelConfigChanges();
+        return this.$q.when();
       }
     };
   }
@@ -277,85 +298,6 @@ export class WorkspaceDetailsController {
     return this.workspaceDetailsService.getPages();
   }
 
-  /**
-   * Returns workspace details section.
-   *
-   * @returns {*}
-   */
-  getSections(): any {
-    return this.workspaceDetailsService.getSections();
-  }
-
-  /**
-   * Callback when workspace config has been changed in editor.
-   *
-   * @param config {che.IWorkspaceConfig} workspace config
-   */
-  updateWorkspaceConfigImport(config: che.IWorkspaceConfig): void {
-    if (!config) {
-      return;
-    }
-    if (angular.equals(this.workspaceDetails.config, config)) {
-      return;
-    }
-    if (this.newName !== config.name) {
-      this.newName = config.name;
-    }
-
-    if (config.defaultEnv && config.environments[config.defaultEnv]) {
-      this.workspaceImportedRecipe = config.environments[config.defaultEnv].recipe;
-    } else if (Object.keys(config.environments).length > 0) {
-      return;
-    }
-
-    this.workspaceDetails.config = config;
-
-
-    if (!this.originWorkspaceDetails || !this.workspaceDetails) {
-      return;
-    }
-
-    // check for failed tabs
-    const failedTabs = this.checkForFailedTabs();
-    // publish changes
-    this.workspaceDetailsService.publishWorkspaceChange(this.workspaceDetails);
-
-    if (!failedTabs || failedTabs.length === 0) {
-      let runningWorkspace = this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.STARTING] || this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.RUNNING];
-      this.saveConfigChanges(false, runningWorkspace);
-    }
-  }
-
-  /**
-   * Callback when workspace devfile has been changed in editor.
-   *
-   * @param {che.IWorkspaceDevfile} devfile workspace devfile
-   */
-  updateWorkspaceDevfile(devfile: che.IWorkspaceDevfile): void {
-    if (!devfile) {
-      return;
-    }
-    if (angular.equals(this.workspaceDetails.devfile, devfile)) {
-      return;
-    }
-
-    if (this.newName !== devfile.metadata.name) {
-      this.newName = devfile.metadata.name;
-    }
-
-    this.workspaceDetails.devfile = devfile;
-
-
-    if (!this.originWorkspaceDetails || !this.workspaceDetails) {
-      return;
-    }
-
-    this.workspaceDetailsService.publishWorkspaceChange(this.workspaceDetails);
-
-    let runningWorkspace = this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.STARTING] || this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.RUNNING];
-    this.saveConfigChanges(false, runningWorkspace);
-  }
-
   /**
    * This method checks form validity on each tab and returns <code>true</code> if
    * all forms are valid.
@@ -363,26 +305,29 @@ export class WorkspaceDetailsController {
    * @returns {string[]} list of names of failed tabs.
    */
   checkForFailedTabs(): string[] {
-    const failTabs = [];
     const tabs = Object.keys(this.tab).filter((tabKey: string) => {
       return !isNaN(parseInt(tabKey, 10));
     });
     tabs.forEach((tabKey: string) => {
-      if (this.checkFormsNotValid(tabKey)) {
-        failTabs.push(this.tab[tabKey]);
+      const tabNotValid = this.checkFormsNotValid(tabKey);
+      const tabName = this.tab[tabKey];
+      const index = this.failedTabs.indexOf(tabName);
+      if (tabNotValid && index === -1) {
+        this.failedTabs.push(tabName);
+      }
+      if (tabNotValid === false && index !== -1) {
+        this.failedTabs.splice(index, 1);
       }
     });
-
-    return failTabs;
+    return this.failedTabs;
   }
 
   /**
    * Builds and returns message for edit-mode-overlay component.
    *
-   * @param {string[]=} failedTabs list of names of failed tabs.
    * @returns {string}
    */
-  getOverlayMessage(failedTabs?: string[]): string {
+  getOverlayMessage(): string {
     if (!this.isSupportedRecipeType) {
       return `Current infrastructure doesn't support this workspace recipe type.`;
     }
@@ -391,11 +336,11 @@ export class WorkspaceDetailsController {
       return `This workspace is using old definition format which is not compatible anymore.`;
     }
 
-    if (failedTabs && failedTabs.length > 0) {
+    if (this.failedTabs.length > 0) {
       const url = this.$location.absUrl().split('?')[0];
       let message = `<i class="error fa fa-exclamation-circle"
           aria-hidden="true"></i>&nbsp;Impossible to save and apply the configuration. Errors in `;
-      message += failedTabs.map((tab: string) => {
+      message += this.failedTabs.map((tab: string) => {
         return `<a href='${url}?tab=${tab}'>${tab}</a>`;
       }).join(', ');
 
@@ -407,42 +352,42 @@ export class WorkspaceDetailsController {
 
   /**
    * Updates config of edit-mode-overlay component.
-   *
-   * @param {boolean} configIsDiffer <code>true</code> if config is differ
-   * @param {string[]=} failedTabs list of names of failed tabs.
    */
-  updateEditModeOverlayConfig(configIsDiffer: boolean, failedTabs?: string[]): void {
-    const formIsValid = !failedTabs || failedTabs.length === 0;
+  updateEditModeOverlayConfig(workspaceIsModified: boolean): void {
+    // check for failed tabs
+    const formIsValid = !this.failedTabs || this.failedTabs.length === 0;
 
     // panel
     this.editOverlayConfig.disabled = !formIsValid || this.loading;
-    this.editOverlayConfig.visible = configIsDiffer || this.workspaceDetailsService.getRestartToApply(this.workspaceId);
+    this.editOverlayConfig.visible = workspaceIsModified || this.workspaceDetailsService.doesWorkspaceConfigNeedRestart(this.workspaceId) === true;
 
     // 'save' button
-    this.editOverlayConfig.saveButton.disabled = !this.isSupported || !configIsDiffer;
+    const saveButtonDisabled = !this.isSupported || !workspaceIsModified;
+    this.editOverlayConfig.saveButton.disabled = saveButtonDisabled;
 
     // 'apply' button
     this.editOverlayConfig.applyButton.disabled = !this.isSupported
-      || (!this.unsavedChangesToApply && !this.workspaceDetailsService.getRestartToApply(this.workspaceId));
+      || (this.workspaceDetailsService.doesWorkspaceConfigNeedRestart(this.workspaceId) === false);
 
     // 'cancel' button
-    this.editOverlayConfig.cancelButton.disabled = !configIsDiffer;
+    this.editOverlayConfig.cancelButton.disabled = !workspaceIsModified;
 
     // message content
-    this.editOverlayConfig.message.content = this.getOverlayMessage(failedTabs);
+    this.editOverlayConfig.message.content = this.getOverlayMessage();
 
     // message visibility
     this.editOverlayConfig.message.visible = !this.isSupported
-      || failedTabs.length > 0
-      || this.unsavedChangesToApply
-      || this.workspaceDetailsService.getRestartToApply(this.workspaceId);
+      || this.failedTabs.length > 0
+      || this.workspaceDetailsService.doesWorkspaceConfigNeedRestart(this.workspaceId) === true;
+
+    this.editOverlayConfig.preventPageLeave = saveButtonDisabled === false;
   }
 
   /**
    * Checks editing mode for workspace config.
    */
-  checkEditMode(restartToApply?: boolean): ng.IPromise<any> {
-    if (!this.originWorkspaceDetails || !this.workspaceDetails) {
+  checkEditMode(): ng.IPromise<void> {
+    if (!this.initialWorkspaceDetails || !this.workspaceDetails) {
       return;
     }
 
@@ -451,24 +396,40 @@ export class WorkspaceDetailsController {
     }
 
     return this.tabsValidationTimeout = this.$timeout(() => {
-      const configIsDiffer = this.originWorkspaceDetails.config ? !angular.equals(this.originWorkspaceDetails.config, this.workspaceDetails.config) : !angular.equals(this.originWorkspaceDetails.devfile, this.workspaceDetails.devfile);
+      this.onWorkspaceChanged();
+    }, 500);
+  }
 
-      // the workspace should be restarted only if its status is STARTING or RUNNING
-      if (this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.STARTING] || this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.RUNNING]) {
-        this.unsavedChangesToApply = configIsDiffer && (this.unsavedChangesToApply || !!restartToApply);
-      } else {
-        this.unsavedChangesToApply = false;
-      }
+  onWorkspaceChanged(): void {
+    let isModified: boolean;
+    let needRestart: boolean;
+    if (this.initialWorkspaceDetails.config) {
+      ({ isModified, needRestart } = this.isModifiedConfig());
+    } else {
+      ({ isModified, needRestart } = this.isModifiedDevfile());
+    }
 
-      // check for failed tabs
-      const failedTabs = this.checkForFailedTabs();
-      // update overlay
-      this.updateEditModeOverlayConfig(configIsDiffer, failedTabs);
-      // update info(editor and plugins)
-      this.updateDeprecatedInfo();
-      // publish changes
-      this.workspaceDetailsService.publishWorkspaceChange(this.workspaceDetails);
-    }, 500);
+    if (this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.STARTING]
+      || this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.RUNNING]) {
+      needRestart = needRestart || this.workspaceDetailsService.doesWorkspaceConfigNeedRestart(this.workspaceId);
+    } else {
+      needRestart = false;
+    }
+
+    if (isModified || needRestart) {
+      this.workspaceDetailsService.setModified(this.workspaceId, { isSaved: isModified === false, needRestart });
+    } else {
+      this.workspaceDetailsService.removeModified(this.workspaceId);
+    }
+
+    // update overlay
+    this.updateEditModeOverlayConfig(isModified);
+
+    // update info(editor and plugins)
+    this.updateDeprecatedInfo();
+
+    // publish changes
+    this.workspaceDetailsService.publishWorkspaceChange(this.workspaceDetails);
   }
 
   /**
@@ -478,27 +439,27 @@ export class WorkspaceDetailsController {
     this.editOverlayConfig.disabled = true;
 
     this.loading = true;
-    this.$scope.$broadcast('edit-workspace-details', {status: 'saving'});
+    this.$scope.$broadcast('edit-workspace-details', { status: 'saving' });
 
     this.workspaceDetailsService.applyConfigChanges(this.workspaceDetails)
       .then(() => {
-        this.workspaceDetailsService.removeRestartToApply(this.workspaceId);
-        this.unsavedChangesToApply = false;
-
+        this.workspaceDetailsService.removeModified(this.workspaceId);
         this.cheNotification.showInfo('Workspace updated.');
-        this.$scope.$broadcast('edit-workspace-details', {status: 'saved'});
-
-        return this.cheWorkspace.fetchWorkspaceDetails(this.originWorkspaceDetails.id).then(() => {
-          this.$location.path('/workspace/' + this.namespaceId + '/' + this.workspaceDetails.config.name).search({tab: this.tab[this.selectedTabIndex]});
-        });
+        this.$scope.$broadcast('edit-workspace-details', { status: 'saved' });
       })
       .catch((error: any) => {
-        this.$scope.$broadcast('edit-workspace-details', {status: 'failed'});
+        this.$scope.$broadcast('edit-workspace-details', { status: 'failed' });
         this.cheNotification.showError('Update workspace failed.', error);
-        return this.checkEditMode(true);
+      })
+      .then(() => {
+        return this.cheWorkspace.fetchWorkspaceDetails(this.initialWorkspaceDetails.id);
+      })
+      .then(() => {
+        this.$location.path('/workspace/' + this.namespaceId + '/' + this.workspaceDataManager.getName(this.workspaceDetails)).search({ tab: this.tab[this.selectedTabIndex] });
       })
       .finally(() => {
         this.loading = false;
+        return this.onWorkspaceChanged();
       });
   }
 
@@ -506,43 +467,33 @@ export class WorkspaceDetailsController {
    * Updates workspace with new config.
    *
    */
-  saveConfigChanges(refreshPage: boolean, notifyRestart?: boolean): void {
+  saveConfigChanges(): void {
+    const notifyRestart = this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.STARTING]
+      || this.getWorkspaceStatus() === WorkspaceStatus[WorkspaceStatus.RUNNING];
+
     this.editOverlayConfig.disabled = true;
 
     this.loading = true;
-    this.$scope.$broadcast('edit-workspace-details', {status: 'saving'});
+    this.$scope.$broadcast('edit-workspace-details', { status: 'saving' });
 
     this.workspaceDetailsService.saveConfigChanges(this.workspaceDetails)
       .then(() => {
-        if (this.unsavedChangesToApply) {
-          this.workspaceDetailsService.addRestartToApply(this.workspaceId);
-        } else {
-          this.workspaceDetailsService.removeRestartToApply(this.workspaceId);
-        }
-        this.unsavedChangesToApply = false;
+        this.workspaceDetailsService.setModified(this.workspaceId, { isSaved: true });
 
         let message = 'Workspace updated.';
         message += notifyRestart ? '<br/>To apply changes in running workspace - need to restart it.' : '';
         this.cheNotification.showInfo(message);
 
-        this.$scope.$broadcast('edit-workspace-details', {status: 'saved'});
-
-        if (refreshPage) {
-          return this.cheWorkspace.fetchWorkspaceDetails(this.originWorkspaceDetails.id).then(() => {
-            let name = this.cheWorkspace.getWorkspaceDataManager().getName(this.workspaceDetails);
-            this.$location.path('/workspace/' + this.namespaceId + '/' + name).search({tab: this.tab[this.selectedTabIndex]});
-          });
-        }
+        this.$scope.$broadcast('edit-workspace-details', { status: 'saved' });
       })
       .catch((error: any) => {
-        this.$scope.$broadcast('edit-workspace-details', {status: 'failed'});
-        const errorMessage = 'Cannot update workspace configuration.';
+        this.$scope.$broadcast('edit-workspace-details', { status: 'failed' });
+        const errorMessage = 'Cannot retrieve workspace configuration.';
         this.cheNotification.showError(error && error.data && error.data.message ? error.data.message : errorMessage);
       })
       .finally(() => {
         this.loading = false;
-
-        return this.checkEditMode();
+        return this.onWorkspaceChanged();
       });
   }
 
@@ -550,25 +501,27 @@ export class WorkspaceDetailsController {
    * Cancels workspace config changes that weren't stored
    */
   cancelConfigChanges(): void {
-    this.editOverlayConfig.disabled = true;
-    this.unsavedChangesToApply = false;
-
-    this.workspaceDetails = angular.copy(this.originWorkspaceDetails);
-
-    this.checkEditMode();
-
-    this.$scope.$broadcast('edit-workspace-details', {status: 'cancelled'});
+    this.workspaceDetailsService.removeModified(this.workspaceId);
+    this.workspaceDetails = angular.copy(this.initialWorkspaceDetails);
+    this.onWorkspaceChanged();
+    this.$scope.$broadcast('edit-workspace-details', { status: 'cancelled' });
   }
 
-  runWorkspace(): ng.IPromise<any> {
+  runWorkspace(): ng.IPromise<void> {
     this.errorMessage = '';
 
+    if (this.workspaceDetailsService.isWorkspaceModified(this.workspaceId)) {
+      return this.workspaceDetailsService.notifyUnsavedChangesDialog();
+    }
     return this.workspaceDetailsService.runWorkspace(this.workspaceDetails).catch((error: any) => {
       this.errorMessage = error.message;
     });
   }
 
-  stopWorkspace(): ng.IPromise<any> {
+  stopWorkspace(): ng.IPromise<void> {
+    if (this.workspaceDetailsService.isWorkspaceModified(this.workspaceId)) {
+      return this.workspaceDetailsService.notifyUnsavedChangesDialog();
+    }
     return this.workspaceDetailsService.stopWorkspace(this.workspaceDetails.id);
   }
 
@@ -593,17 +546,38 @@ export class WorkspaceDetailsController {
     return form && form.$invalid;
   }
 
-  /**
-   * Returns true when 'Save' button should be disabled
-   *
-   * @returns {boolean}
-   */
-  isSaveButtonDisabled(): boolean {
-    const tabs = Object.keys(this.tab).filter((tabKey: string) => {
-      return !isNaN(parseInt(tabKey, 10));
-    });
+  private isModifiedConfig(): { isModified: boolean, needRestart: boolean } {
+    const isEqual = angular.equals(this.initialWorkspaceDetails.config, this.workspaceDetails.config);
+    if (isEqual) {
+      return {
+        isModified: false,
+        needRestart: false
+      };
+    }
 
-    return tabs.some((tabKey: string) => this.checkFormsNotValid(tabKey));
+    const tmpConfig = angular.extend({}, this.initialWorkspaceDetails.config, { name: this.workspaceDetails.config.name });
+    const needRestart = false === angular.equals(tmpConfig, this.workspaceDetails.config);
+    return {
+      isModified: true,
+      needRestart
+    };
+  }
+
+  private isModifiedDevfile(): { isModified: boolean, needRestart: boolean } {
+    const isEqual = angular.equals(this.initialWorkspaceDetails.devfile, this.workspaceDetails.devfile);
+    if (isEqual) {
+      return {
+        isModified: false,
+        needRestart: false
+      };
+    }
+
+    const tmpDevfile = angular.extend({}, this.initialWorkspaceDetails.devfile, { metadata: { name: this.workspaceDetails.devfile.metadata.name } });
+    const needRestart = false === angular.equals(tmpDevfile, this.workspaceDetails.devfile);
+    return {
+      isModified: true,
+      needRestart
+    };
   }
 
   /**
@@ -638,4 +612,5 @@ export class WorkspaceDetailsController {
     const deprecatedPlugins = this.workspaceDetailsService.getSelectedDeprecatedPlugins(this.workspaceDetails);
     this.hasSelectedDeprecatedPlugins = deprecatedPlugins.length > 0;
   }
+
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts b/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts
index 843b81ff6b..3a4c786db0 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.directive.spec.ts
@@ -281,12 +281,14 @@ describe(`WorkspaceDetailsController >`, () => {
     angular.mock.module('workspaceDetailsMock');
   });
 
-  beforeEach(inject((_$rootScope_: ng.IRootScopeService,
-                     _$compile_: ng.ICompileService,
-                     _cheHttpBackend_: CheHttpBackend,
-                     _$timeout_: ng.ITimeoutService,
-                     _$q_: ng.IQService,
-                     _cheWorkspace_: CheWorkspace) => {
+  beforeEach(inject((
+    _$rootScope_: ng.IRootScopeService,
+    _$compile_: ng.ICompileService,
+    _cheHttpBackend_: CheHttpBackend,
+    _$timeout_: ng.ITimeoutService,
+    _$q_: ng.IQService,
+    _cheWorkspace_: CheWorkspace
+  ) => {
     $scope = _$rootScope_.$new();
     $compile = _$compile_;
     $timeout = _$timeout_;
@@ -320,6 +322,12 @@ describe(`WorkspaceDetailsController >`, () => {
     $httpBackend.verifyNoOutstandingRequest();
   });
 
+  afterEach(() => {
+    compiledDirective = undefined;
+    cheWorkspace = undefined;
+    newWorkspace = undefined;
+  });
+
   describe(`overflow panel >`, () => {
 
     function getOverlayPanelEl(): ng.IAugmentedJQuery {
@@ -335,9 +343,20 @@ describe(`WorkspaceDetailsController >`, () => {
       return compiledDirective.find('.cancel-button button');
     }
 
-    it(`should be hidden initially >`, () => {
-      compileDirective();
-      expect(getOverlayPanelEl().children().length).toEqual(0);
+    describe('initially >', () => {
+
+      beforeEach(() => {
+        compileDirective();
+      });
+
+      it(`should be hidden >`, () => {
+        expect(getOverlayPanelEl().children().length).toEqual(0);
+      });
+
+      it('should not prevent to leave page', () => {
+        expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+      });
+
     });
 
     describe(`when config is changed >`, () => {
@@ -349,12 +368,16 @@ describe(`WorkspaceDetailsController >`, () => {
           beforeEach(() => {
             compileDirective();
 
-            controller.workspaceDetails.config.name = 'wksp-new-name';
-            controller.checkEditMode(false);
+            (controller as any).workspaceDetails.config.name = 'wksp-new-name';
+            controller.checkEditMode();
             $scope.$digest();
             $timeout.flush();
           });
 
+          it('should prevent to leave page', () => {
+            expect((controller as any).editOverlayConfig.preventPageLeave).toBeTruthy();
+          });
+
           it(`the overflow panel should be shown >`, () => {
             expect(getOverlayPanelEl().length).toEqual(1);
           });
@@ -376,7 +399,10 @@ describe(`WorkspaceDetailsController >`, () => {
             beforeEach(() => {
               getCancelButton().click();
               $scope.$digest();
-              $timeout.flush();
+            });
+
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
             });
 
             it(`the overlay panel should be hidden >`, () => {
@@ -389,13 +415,17 @@ describe(`WorkspaceDetailsController >`, () => {
 
             beforeEach(() => {
               // set new workspace to publish
-              newWorkspace = angular.copy(controller.workspaceDetails);
+              newWorkspace = angular.copy((controller as any).workspaceDetails);
 
               getSaveButton().click();
               $scope.$digest();
               $timeout.flush();
             });
 
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+            });
+
             it(`the overlay panel should be hidden >`, () => {
               expect(getOverlayPanelEl().children().length).toEqual(0);
             });
@@ -409,12 +439,16 @@ describe(`WorkspaceDetailsController >`, () => {
           beforeEach(() => {
             compileDirective();
 
-            controller.workspaceDetails.config.name = 'wksp-new-name';
-            controller.checkEditMode(true);
+            (controller as any).workspaceDetails.config.defaultEnv = 'new-env';
+            controller.checkEditMode();
             $scope.$digest();
             $timeout.flush();
           });
 
+          it('should prevent to leave page', () => {
+            expect((controller as any).editOverlayConfig.preventPageLeave).toBeTruthy();
+          });
+
           it(`the overflow panel should be shown >`, () => {
             expect(getOverlayPanelEl().length).toEqual(1);
           });
@@ -436,7 +470,10 @@ describe(`WorkspaceDetailsController >`, () => {
             beforeEach(() => {
               getCancelButton().click();
               $scope.$digest();
-              $timeout.flush();
+            });
+
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
             });
 
             it(`the overlay panel should be hidden >`, () => {
@@ -448,11 +485,16 @@ describe(`WorkspaceDetailsController >`, () => {
           describe(`and saveButton is clicked >`, () => {
 
             beforeEach(() => {
+              newWorkspace = angular.copy((controller as any).workspaceDetails);
               getSaveButton().click();
               $scope.$digest();
               $timeout.flush();
             });
 
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+            });
+
             it(`the overlay panel should remain visible >`, () => {
               expect(getOverlayPanelEl().length).toEqual(1);
             });
@@ -475,13 +517,17 @@ describe(`WorkspaceDetailsController >`, () => {
 
             beforeEach(() => {
               // set new workspace to publish
-              newWorkspace = angular.copy(controller.workspaceDetails);
+              newWorkspace = angular.copy((controller as any).workspaceDetails);
 
               getApplyButton().click();
               $scope.$digest();
               $timeout.flush();
             });
 
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+            });
+
             it(`the overlay panel should be hidden >`, () => {
               expect(getOverlayPanelEl().children().length).toEqual(0);
             });
@@ -498,17 +544,21 @@ describe(`WorkspaceDetailsController >`, () => {
           compileDirective();
 
           controller.stopWorkspace();
-          controller.workspaceDetails.config.name = 'wksp-new-name';
+          (controller as any).workspaceDetails.config.name = 'wksp-new-name';
         });
 
         describe(`and restart is not necessary >`, () => {
 
           beforeEach(() => {
-            controller.checkEditMode(false);
+            controller.checkEditMode();
             $scope.$digest();
             $timeout.flush();
           });
 
+          it('should prevent to leave page', () => {
+            expect((controller as any).editOverlayConfig.preventPageLeave).toBeTruthy();
+          });
+
           it(`the overflow panel should be shown >`, () => {
             expect(getOverlayPanelEl().length).toEqual(1);
           });
@@ -530,7 +580,10 @@ describe(`WorkspaceDetailsController >`, () => {
             beforeEach(() => {
               getCancelButton().click();
               $scope.$digest();
-              $timeout.flush();
+            });
+
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
             });
 
             it(`the overlay panel should be hidden >`, () => {
@@ -543,13 +596,17 @@ describe(`WorkspaceDetailsController >`, () => {
 
             beforeEach(() => {
               // set new workspace to publish
-              newWorkspace = angular.copy(controller.workspaceDetails);
+              newWorkspace = angular.copy((controller as any).workspaceDetails);
 
               getSaveButton().click();
               $scope.$digest();
               $timeout.flush();
             });
 
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+            });
+
             it(`the overlay panel should be hidden >`, () => {
               expect(getOverlayPanelEl().children().length).toEqual(0);
             });
@@ -561,11 +618,15 @@ describe(`WorkspaceDetailsController >`, () => {
         describe(`and restart is necessary >`, () => {
 
           beforeEach(() => {
-            controller.checkEditMode(true);
+            controller.checkEditMode();
             $scope.$digest();
             $timeout.flush();
           });
 
+          it('should prevent to leave page', () => {
+            expect((controller as any).editOverlayConfig.preventPageLeave).toBeTruthy();
+          });
+
           it(`the overflow panel should be shown >`, () => {
             expect(getOverlayPanelEl().length).toEqual(1);
           });
@@ -587,7 +648,10 @@ describe(`WorkspaceDetailsController >`, () => {
             beforeEach(() => {
               getCancelButton().click();
               $scope.$digest();
-              $timeout.flush();
+            });
+
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
             });
 
             it(`the overlay panel should be hidden >`, () => {
@@ -600,13 +664,17 @@ describe(`WorkspaceDetailsController >`, () => {
 
             beforeEach(() => {
               // set new workspace to publish
-              newWorkspace = angular.copy(controller.workspaceDetails);
+              newWorkspace = angular.copy((controller as any).workspaceDetails);
 
               getSaveButton().click();
               $scope.$digest();
               $timeout.flush();
             });
 
+            it('should not prevent to leave page', () => {
+              expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+            });
+
             it(`the overlay panel should be hidden >`, () => {
               expect(getOverlayPanelEl().children().length).toEqual(0);
             });
@@ -622,23 +690,27 @@ describe(`WorkspaceDetailsController >`, () => {
         beforeEach(() => {
           compileDirective();
 
-          controller.workspacesService.isSupported = jasmine.createSpy('workspaceDetailsController.isSupported')
+          (controller as any).workspacesService.isSupported = jasmine.createSpy('workspaceDetailsController.isSupported')
             .and
             .callFake(() => {
               return false;
             });
-          controller.workspacesService.isSupportedRecipeType = jasmine.createSpy('workspaceDetailsController.isSupportedRecipeType')
+          (controller as any).workspacesService.isSupportedRecipeType = jasmine.createSpy('workspaceDetailsController.isSupportedRecipeType')
             .and
             .callFake(() => {
               return false;
             });
 
-          controller.workspaceDetails.config.name = 'wksp-new-name';
-          controller.checkEditMode(false);
+          (controller as any).workspaceDetails.config.name = 'wksp-new-name';
+          controller.checkEditMode();
           $scope.$digest();
           $timeout.flush();
         });
 
+        it('should not prevent to leave page', () => {
+          expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
+        });
+
         it(`the overflow panel should be shown >`, () => {
           expect(getOverlayPanelEl().length).toEqual(1);
         });
@@ -660,7 +732,10 @@ describe(`WorkspaceDetailsController >`, () => {
           beforeEach(() => {
             getCancelButton().click();
             $scope.$digest();
-            $timeout.flush();
+          });
+
+          it('should not prevent to leave page', () => {
+            expect((controller as any).editOverlayConfig.preventPageLeave).toBeFalsy();
           });
 
           it(`the overlay panel should be hidden >`, () => {
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.html b/dashboard/src/app/workspaces/workspace-details/workspace-details.html
index 3679ead506..3f0e2f07d0 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-details.html
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.html
@@ -42,7 +42,7 @@
         <ng-form name="workspaceOverviewForm">
           <workspace-details-overview ng-init="workspaceDetailsController.setForm(workspaceDetailsController.tab.Overview, workspaceOverviewForm)"
                                       overview-form="workspaceOverviewForm"
-                                      on-change="workspaceDetailsController.checkEditMode(false)"
+                                      on-change="workspaceDetailsController.onWorkspaceChanged()"
                                       workspace-details="workspaceDetailsController.workspaceDetails"></workspace-details-overview>
         </ng-form>
       </md-tab-body>
@@ -56,7 +56,7 @@
       <md-tab-body>
         <workspace-details-projects
           workspace-details="workspaceDetailsController.workspaceDetails"
-          projects-on-change="workspaceDetailsController.checkEditMode(true)"></workspace-details-projects>
+          projects-on-change="workspaceDetailsController.checkEditMode()"></workspace-details-projects>
       </md-tab-body>
     </md-tab>
 
@@ -68,7 +68,7 @@
       </md-tab-label>
       <md-tab-body>
         <workspace-machines workspace-details="workspaceDetailsController.workspaceDetails"
-                            on-change="workspaceDetailsController.checkEditMode(true)"></workspace-machines>
+                            on-change="workspaceDetailsController.checkEditMode()"></workspace-machines>
       </md-tab-body>
     </md-tab>
 
@@ -81,7 +81,7 @@
       <md-tab-body>
         <che-machine-selector content-title="Servers"
                               workspace-details="workspaceDetailsController.workspaceDetails"
-                              on-change="workspaceDetailsController.checkEditMode(true)">
+                              on-change="workspaceDetailsController.checkEditMode()">
           <che-machine-servers environment-manager="environmentManager"
                                selected-machine="machine"
                                on-change="onChange()"></che-machine-servers>
@@ -98,7 +98,7 @@
       <md-tab-body>
         <che-machine-selector content-title="Environment variables"
                               workspace-details="workspaceDetailsController.workspaceDetails"
-                              on-change="workspaceDetailsController.checkEditMode(true)">
+                              on-change="workspaceDetailsController.checkEditMode()">
           <che-env-variables environment-manager="environmentManager"
                              selected-machine="machine"
                              on-change="onChange()"></che-env-variables>
@@ -115,7 +115,7 @@
       <md-tab-body>
         <che-machine-selector content-title="Volumes"
                               workspace-details="workspaceDetailsController.workspaceDetails"
-                              on-change="workspaceDetailsController.checkEditMode(true)">
+                              on-change="workspaceDetailsController.checkEditMode()">
           <che-machine-volumes environment-manager="environmentManager"
                              selected-machine="machine"
                              on-change="onChange()"></che-machine-volumes>
@@ -167,7 +167,7 @@
       <md-tab-body>
         <workspace-plugins workspace="workspaceDetailsController.workspaceDetails"
                            plugin-registry-location="workspaceDetailsController.pluginRegistry"
-                           on-change="workspaceDetailsController.checkEditMode(true)">
+                           on-change="workspaceDetailsController.checkEditMode()">
         </workspace-plugins>
       </md-tab-body>
     </md-tab>
@@ -183,31 +183,31 @@
       <md-tab-body>
         <workspace-editors workspace="workspaceDetailsController.workspaceDetails"
                            plugin-registry-location="workspaceDetailsController.pluginRegistry"
-                           on-change="workspaceDetailsController.checkEditMode(true)">
+                           on-change="workspaceDetailsController.checkEditMode()">
         </workspace-editors>
       </md-tab-body>
     </md-tab>
 
-     <!-- Devfile tab -->
-     <md-tab ng-if="workspaceDetailsController.workspaceDetails.devfile"
-     md-on-select="workspaceDetailsController.onSelectTab(workspaceDetailsController.tab.Devfile); isDevfileActive = true"
-     md-on-deselect="isDevfileActive = false">
-     <md-tab-label>
-       <i ng-show="workspaceDetailsController.checkFormsNotValid(workspaceDetailsController.tab.Devfile)"
-          class="error-state fa fa-exclamation-circle" aria-hidden="true"></i>
-       <span class="che-tab-label-title">Devfile</span>
-     </md-tab-label>
-     <md-tab-body>
-        <che-label-container che-label-name="Workspace">
-            <ng-form name="workspaceDevfileForm">
-              <workspace-devfile-editor
-                is-active="isDevfileActive"
-                workspace-devfile="workspaceDetailsController.workspaceDetails.devfile"
-                workspace-devfile-on-change="workspaceDetailsController.updateWorkspaceDevfile(devfile)"></workspace-devfile-editor>
-            </ng-form>
-          </che-label-container>
-     </md-tab-body>
-   </md-tab>
+    <!-- Devfile tab -->
+    <md-tab ng-if="workspaceDetailsController.workspaceDetails.devfile"
+            md-on-select="workspaceDetailsController.onSelectTab(workspaceDetailsController.tab.Devfile); isDevfileActive = true"
+            md-on-deselect="isDevfileActive = false">
+    <md-tab-label>
+      <i ng-show="workspaceDetailsController.checkFormsNotValid(workspaceDetailsController.tab.Devfile)"
+        class="error-state fa fa-exclamation-circle" aria-hidden="true"></i>
+      <span class="che-tab-label-title">Devfile</span>
+    </md-tab-label>
+    <md-tab-body>
+      <che-label-container che-label-name="Workspace">
+        <ng-form name="workspaceDevfileForm">
+          <workspace-devfile-editor
+            is-active="isDevfileActive"
+            workspace-devfile="workspaceDetailsController.workspaceDetails.devfile"
+            workspace-devfile-on-change="workspaceDetailsController.onWorkspaceChanged()"></workspace-devfile-editor>
+          </ng-form>
+        </che-label-container>
+      </md-tab-body>
+    </md-tab>
 
     <!-- Other tabs -->
     <md-tab ng-repeat="section in workspaceDetailsController.getPages()"
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts b/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts
index 85cf7ff694..81d1ea02ac 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-details.service.ts
@@ -12,13 +12,13 @@
 'use strict';
 import {CheNotification} from '../../../components/notification/che-notification.factory';
 import IdeSvc from '../../ide/ide.service';
-import {CreateWorkspaceSvc} from '../create-workspace/create-workspace.service';
 import {IObservable, IObservableCallbackFn, Observable} from '../../../components/utils/observable';
 import {WorkspaceDetailsProjectsService} from './workspace-projects/workspace-details-projects.service';
 import {CheWorkspace, WorkspaceStatus} from '../../../components/api/workspace/che-workspace.factory';
 import {CheService} from '../../../components/api/che-service.factory';
 import {PluginRegistry} from '../../../components/api/plugin-registry.factory';
 import {WorkspaceDataManager} from '../../../components/api/workspace/workspace-data-manager';
+import { ConfirmDialogService } from '../../../components/service/confirm-dialog/confirm-dialog.service';
 
 interface IPage {
   title: string;
@@ -28,9 +28,49 @@ interface IPage {
 }
 
 interface ISection {
- title: string;
- description: string;
- content: string;
+  title: string;
+  description: string;
+  content: string;
+}
+
+export interface IModifiedWorkspace {
+  isSaved: boolean;
+  needRestart?: boolean;
+}
+
+class ModifiedWorkspaces {
+  workspaces: { [id: string]: IModifiedWorkspace } = {};
+
+  set(id: string, attrs: { isSaved?: boolean, needRestart?: boolean }): void {
+    if (this.workspaces[id] === undefined) {
+      this.workspaces[id] = { isSaved: false };
+    }
+    if (angular.isDefined(attrs.isSaved)) {
+      this.workspaces[id].isSaved = attrs.isSaved;
+    }
+    if (angular.isDefined(attrs.needRestart)) {
+      this.workspaces[id].needRestart = attrs.needRestart;
+    }
+  }
+
+  isSaved(id: string): boolean {
+    if (this.workspaces[id] === undefined) {
+      return true;
+    }
+    return this.workspaces[id].isSaved;
+  }
+
+  needRestart(id: string): boolean {
+    if (this.workspaces[id] === undefined) {
+      return false;
+    }
+    return this.workspaces[id].needRestart;
+  }
+
+  remove(id: string): void {
+    delete this.workspaces[id];
+  }
+
 }
 
 /**
@@ -41,7 +81,18 @@ interface ISection {
  */
 export class WorkspaceDetailsService {
 
-  static $inject = ['$log', '$q', 'cheWorkspace', 'cheNotification', 'ideSvc', 'createWorkspaceSvc', 'workspaceDetailsProjectsService', 'cheService', 'chePermissions', 'pluginRegistry'];
+  static $inject = [
+    '$log',
+    '$q',
+    'cheWorkspace',
+    'cheNotification',
+    'ideSvc',
+    'workspaceDetailsProjectsService',
+    'cheService',
+    'chePermissions',
+    'confirmDialogService',
+    'pluginRegistry'
+  ];
 
   /**
    * Logging service.
@@ -63,14 +114,14 @@ export class WorkspaceDetailsService {
    * IDE service.
    */
   private ideSvc: IdeSvc;
-  /**
-   * Workspace creation service.
-   */
-  private createWorkspaceSvc: CreateWorkspaceSvc;
   /**
    * Service for projects of workspace.
    */
   private workspaceDetailsProjectsService: WorkspaceDetailsProjectsService;
+  /**
+   * Confirm dialog service.
+   */
+  private confirmDialogService: ConfirmDialogService;
   /**
    * Instance of Observable.
    */
@@ -79,9 +130,9 @@ export class WorkspaceDetailsService {
   private pages: IPage[];
   private sections: ISection[];
   /**
-   * This workspaces should be restarted for new config to be applied.
+   * These workspaces should be restarted for new config to be applied.
    */
-  private restartToApply: string[] = [];
+  private modifiedWorkspaces: ModifiedWorkspaces = new ModifiedWorkspaces();
   /**
    * Array of deprecated editors.
    */
@@ -108,10 +159,10 @@ export class WorkspaceDetailsService {
     cheWorkspace: CheWorkspace,
     cheNotification: CheNotification,
     ideSvc: IdeSvc,
-    createWorkspaceSvc: CreateWorkspaceSvc,
     workspaceDetailsProjectsService: WorkspaceDetailsProjectsService,
     cheService: CheService,
     chePermissions: che.api.IChePermissions,
+    confirmDialogService: ConfirmDialogService,
     pluginRegistry: PluginRegistry
   ) {
     this.$log = $log;
@@ -119,9 +170,9 @@ export class WorkspaceDetailsService {
     this.cheWorkspace = cheWorkspace;
     this.cheNotification = cheNotification;
     this.ideSvc = ideSvc;
-    this.createWorkspaceSvc = createWorkspaceSvc;
     this.pluginRegistry = pluginRegistry;
     this.workspaceDetailsProjectsService = workspaceDetailsProjectsService;
+    this.confirmDialogService = confirmDialogService;
 
     this.observable =  new Observable<any>();
 
@@ -288,12 +339,11 @@ export class WorkspaceDetailsService {
         return this.cheWorkspace.fetchStatusChange(workspace.id, WorkspaceStatus[WorkspaceStatus.STOPPED]);
       })
       .then(() => {
-        this.removeRestartToApply(workspace.id);
-
         return this.saveConfigChanges(workspace);
       })
       .then(() => {
-        this.cheWorkspace.startWorkspace(workspace.id, workspace.config.defaultEnv);
+        const envName = workspace.config ? workspace.config.defaultEnv : undefined;
+        this.cheWorkspace.startWorkspace(workspace.id, envName);
         return this.cheWorkspace.fetchStatusChange(workspace.id, WorkspaceStatus[WorkspaceStatus.RUNNING]);
       })
       .catch((error: any) => {
@@ -303,39 +353,39 @@ export class WorkspaceDetailsService {
   }
 
   /**
-   * Add workspace ID to the list of workspaces that should be restarted.
-   *
-   * @param {string} workspaceId
+   * Keep a workspace as one that is modified and may need restarting to apply changes.
    */
-  addRestartToApply(workspaceId: string): void {
-    if (this.restartToApply.indexOf(workspaceId) === -1) {
-      this.restartToApply.push(workspaceId);
-    }
+  setModified(id: string, attrs: { isSaved?: boolean, needRestart?: boolean }): void {
+    this.modifiedWorkspaces.set(id, attrs);
+  }
+
+  removeModified(id: string): void {
+    this.modifiedWorkspaces.remove(id);
   }
 
   /**
-   * Remove workspace ID from the list of workspaces that should be restarted.
-   *
-   * @param {string} workspaceId
+   * Returns `true` if workspace configuration has been already saved.
+   * @param id a workspace ID
    */
-  removeRestartToApply(workspaceId: string): void {
-    const index = this.restartToApply.indexOf(workspaceId);
-    if (index === -1) {
-      return;
-    }
-    this.restartToApply.splice(index, 1);
+  isWorkspaceConfigSaved(id: string): boolean {
+    return this.modifiedWorkspaces.isSaved(id);
   }
 
   /**
-   * Returns <code>true</code> if workspace ID belongs to the list of
-   * workspaces that should be restarted.
-   *
-   * @param {string} workspaceId
-   * @returns {boolean}
+   * Returns `true` if workspace configuration has been already applied.
+   * @param id a workspace ID
+   */
+  doesWorkspaceConfigNeedRestart(id: string): boolean {
+    return this.modifiedWorkspaces.needRestart(id);
+  }
+
+  /**
+   * Returns `true` if workspace configuration was changed.
+   * @param id a workspace ID
    */
-  getRestartToApply(workspaceId: string): boolean {
-    const index = this.restartToApply.indexOf(workspaceId);
-    return index !== -1;
+  isWorkspaceModified(id: string): boolean {
+    return this.modifiedWorkspaces.isSaved(id) === false
+      || this.modifiedWorkspaces.needRestart(id) === true;
   }
 
   /**
@@ -413,4 +463,11 @@ export class WorkspaceDetailsService {
     });
   }
 
+  /**
+   * Shows modal window with notification about unsaved changes.
+   */
+  notifyUnsavedChangesDialog(): ng.IPromise<void> {
+    return this.confirmDialogService.showConfirmDialog('Unsaved Changes', `You're editing this workspace configuration. Please save or discard changes to be able to run or stop the workspace.`, { reject: 'Close' });
+  }
+
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-machine-env-variables/env-variables.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-machine-env-variables/env-variables.controller.ts
index 1e80c10c23..b5a0dc0a51 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-machine-env-variables/env-variables.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-machine-env-variables/env-variables.controller.ts
@@ -190,7 +190,7 @@ export class EnvVariablesController {
    * @param variableName {string}
    */
   deleteEnvVariable(variableName: string): void {
-    const promise = this.confirmDialogService.showConfirmDialog('Remove variable', 'Would you like to delete this variable?', 'Delete');
+    const promise = this.confirmDialogService.showConfirmDialog('Remove variable', 'Would you like to delete this variable?', { resolve: 'Delete' });
     promise.then(() => {
       delete this.envVariables[variableName];
       this.environmentManager.setEnvVariables(this.selectedMachine, this.envVariables);
@@ -212,6 +212,6 @@ export class EnvVariablesController {
       content += 'this selected variable?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove variables', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove variables', content, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-machine-servers/machine-servers.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-machine-servers/machine-servers.controller.ts
index 405f3b984f..485f7f6b84 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-machine-servers/machine-servers.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-machine-servers/machine-servers.controller.ts
@@ -204,7 +204,7 @@ export class MachineServersController {
    * @param reference {string}
    */
   deleteServer(reference: string): void {
-    const promise = this.confirmDialogService.showConfirmDialog('Remove server', 'Would you like to delete this server?', 'Delete');
+    const promise = this.confirmDialogService.showConfirmDialog('Remove server', 'Would you like to delete this server?', { resolve: 'Delete' });
     promise.then(() => {
       delete this.servers[reference];
       this.environmentManager.setServers(this.selectedMachine, this.servers);
@@ -225,6 +225,6 @@ export class MachineServersController {
       content += 'this selected server?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove servers', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove servers', content, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-machine-volumes/machine-volumes.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-machine-volumes/machine-volumes.controller.ts
index 755924465c..01a5cdec46 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-machine-volumes/machine-volumes.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-machine-volumes/machine-volumes.controller.ts
@@ -140,7 +140,7 @@ export class MachineVolumesController {
    * @param variableName {string}
    */
   deleteMachineVolume(variableName: string): void {
-    const promise = this.confirmDialogService.showConfirmDialog('Remove variable', 'Would you like to delete this variable?', 'Delete');
+    const promise = this.confirmDialogService.showConfirmDialog('Remove variable', 'Would you like to delete this variable?', { resolve: 'Delete' });
     promise.then(() => {
       delete this.machineVolumes[variableName];
       this.environmentManager.setMachineVolumes(this.selectedMachine, this.machineVolumes);
@@ -162,6 +162,6 @@ export class MachineVolumesController {
       content += 'this selected variable?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove variables', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove variables', content, { resolve: 'Delete' });
   }
 }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-machines/workspace-machines.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-machines/workspace-machines.controller.ts
index 2dc648b0a1..b2090fa900 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-machines/workspace-machines.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-machines/workspace-machines.controller.ts
@@ -218,7 +218,7 @@ export class WorkspaceMachinesController {
       content += 'these ' + selectedItems.length + ' machines?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove machines', content, 'Delete').then(() => {
+    return this.confirmDialogService.showConfirmDialog('Remove machines', content, { resolve: 'Delete' }).then(() => {
       return selectedItems;
     });
   }
@@ -276,7 +276,7 @@ export class WorkspaceMachinesController {
    * @param name {string}
    */
   deleteMachine(name: string): void {
-    this.confirmDialogService.showConfirmDialog('Remove machine', 'Would you like to delete this machine?', 'Delete').then(() => {
+    this.confirmDialogService.showConfirmDialog('Remove machine', 'Would you like to delete this machine?', { resolve: 'Delete' }).then(() => {
       this.machineOnDelete(name);
     });
   }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.controller.ts
index 9b0c2ccdd0..eb925b0372 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.controller.ts
@@ -272,7 +272,7 @@ export class WorkspaceDetailsOverviewController {
    */
   deleteWorkspace(): void {
     const content = 'Would you like to delete workspace \'' + this.cheWorkspace.getWorkspaceDataManager().getName(this.workspaceDetails) + '\'?';
-    this.confirmDialogService.showConfirmDialog('Delete workspace', content, 'Delete').then(() => {
+    this.confirmDialogService.showConfirmDialog('Delete workspace', content, { resolve: 'Delete' }).then(() => {
       if ([RUNNING, STARTING].indexOf(this.getWorkspaceStatus()) !== -1) {
         this.cheWorkspace.stopWorkspace(this.workspaceDetails.id);
       }
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.html b/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.html
index 5fec462ccd..4b67bdd709 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.html
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-overview/workspace-details-overview.html
@@ -7,7 +7,7 @@
       che-name="name"
       che-place-holder="Name of the workspace"
       aria-label="Name of the workspace"
-      ng-model-options="{ allowInvalid: true }"
+      ng-model-options="{ allowInvalid: true, debounce: 300 }"
       ng-model="workspaceDetailsOverviewController.name"
       che-on-change="workspaceDetailsOverviewController.onNameChange()"
       required
diff --git a/dashboard/src/app/workspaces/workspace-details/workspace-projects/workspace-details-projects.controller.ts b/dashboard/src/app/workspaces/workspace-details/workspace-projects/workspace-details-projects.controller.ts
index 6eb5e76bc6..d3d372f28e 100644
--- a/dashboard/src/app/workspaces/workspace-details/workspace-projects/workspace-details-projects.controller.ts
+++ b/dashboard/src/app/workspaces/workspace-details/workspace-projects/workspace-details-projects.controller.ts
@@ -256,7 +256,7 @@ export class WorkspaceDetailsProjectsCtrl {
       content += 'this selected project?';
     }
 
-    return this.confirmDialogService.showConfirmDialog('Remove projects', content, 'Delete');
+    return this.confirmDialogService.showConfirmDialog('Remove projects', content, { resolve: 'Delete' });
   }
 
   workspaceIsRunning(): boolean {
diff --git a/dashboard/src/components/service/confirm-dialog/che-confirm-dialog.html b/dashboard/src/components/service/confirm-dialog/che-confirm-dialog.html
index 94b28a5db5..8fb81804bb 100644
--- a/dashboard/src/components/service/confirm-dialog/che-confirm-dialog.html
+++ b/dashboard/src/components/service/confirm-dialog/che-confirm-dialog.html
@@ -2,11 +2,12 @@
   <div class="che-confirm-dialog-notification">
     <div>{{cheConfirmDialogController.content}}</div>
     <div layout="row" flex layout-align="end end">
-      <che-button-primary che-button-title="{{cheConfirmDialogController.resolveButtonTitle}}"
+      <che-button-primary ng-if="cheConfirmDialogController.buttons.resolve"
+                          che-button-title="{{cheConfirmDialogController.buttons.resolve}}"
                           id="ok-dialog-button"
                           ng-click="cheConfirmDialogController.hide()">
       </che-button-primary>
-      <che-button-notice che-button-title="{{cheConfirmDialogController.rejectButtonTitle}}"
+      <che-button-notice che-button-title="{{cheConfirmDialogController.buttons.reject}}"
                          id="cancel-dialog-button"
                          ng-click="cheConfirmDialogController.cancel()">
       </che-button-notice>
diff --git a/dashboard/src/components/service/confirm-dialog/confirm-dialog.service.ts b/dashboard/src/components/service/confirm-dialog/confirm-dialog.service.ts
index 7548966ea2..325d9559be 100644
--- a/dashboard/src/components/service/confirm-dialog/confirm-dialog.service.ts
+++ b/dashboard/src/components/service/confirm-dialog/confirm-dialog.service.ts
@@ -34,12 +34,12 @@ export class ConfirmDialogService {
    *
    * @param title{string} popup title
    * @param content{string} dialog content
-   * @param resolveButtonTitle{string} title for resolve button
-   * @param rejectButtonTitle{string} title for reject button
+   * @param buttonTitles dialog buttons titles
    *
    * @returns {ng.IPromise<any>}
    */
-  showConfirmDialog(title: string, content: string, resolveButtonTitle: string, rejectButtonTitle?: string): ng.IPromise<any> {
+  showConfirmDialog(title: string, content: string, buttonTitles?: { resolve?: string, reject?: string }): ng.IPromise<any> {
+    buttonTitles.reject = buttonTitles.reject || 'Close';
     return this.$mdDialog.show({
       bindToController: true,
       clickOutsideToClose: true,
@@ -49,8 +49,7 @@ export class ConfirmDialogService {
         content: content,
         $mdDialog: this.$mdDialog,
         title: title,
-        resolveButtonTitle: resolveButtonTitle,
-        rejectButtonTitle: rejectButtonTitle ? rejectButtonTitle : 'Close'
+        buttons: buttonTitles
       },
       templateUrl: 'components/service/confirm-dialog/che-confirm-dialog.html'
     });
diff --git a/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.controller.ts b/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.controller.ts
new file mode 100644
index 0000000000..d76d3afe87
--- /dev/null
+++ b/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.controller.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2015-2018 Red Hat, Inc.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *   Red Hat, Inc. - initial API and implementation
+ */
+'use strict';
+
+import { ICheEditModeOverlayConfig } from './che-edit-mode-overlay.directive';
+import { ConfirmDialogService } from '../../service/confirm-dialog/confirm-dialog.service';
+
+export class CheEditModeOverlayController {
+
+  static $inject = [
+    '$location',
+    '$scope',
+    'confirmDialogService'
+  ];
+  private $location: ng.ILocationService;
+  private $scope: ng.IScope;
+  private confirmDialogService: ConfirmDialogService;
+
+  private config: ICheEditModeOverlayConfig;
+
+  constructor(
+    $location: ng.ILocationService,
+    $scope: ng.IScope,
+    confirmDialogService: ConfirmDialogService
+  ) {
+    this.$location = $location;
+    this.$scope = $scope;
+    this.confirmDialogService = confirmDialogService;
+
+    this.$scope.$on('$locationChangeStart', (event: ng.IAngularEvent, newUrl: string, oldUrl: string) => {
+      if (this.config && this.config.preventPageLeave === false) {
+        return;
+      }
+
+      // check if path remains the same
+      const oldPath = this.extractPathname(oldUrl);
+      const newPath = this.extractPathname(newUrl);
+      if (oldPath === newPath) {
+        return;
+      }
+
+      event.preventDefault();
+
+      if (typeof this.config.onChangesDiscard !== 'function') {
+        return;
+      }
+      return this.discardUnsavedChangesDialog().then(() => {
+        return this.config.onChangesDiscard().then(() => {
+          const hash = newUrl.slice(newUrl.indexOf('#') + 1, newUrl.length);
+          this.$location.url(hash);
+        });
+      });
+    });
+  }
+
+  discardUnsavedChangesDialog(): ng.IPromise<void> {
+    return this.confirmDialogService.showConfirmDialog('Unsaved Changes', 'You have unsaved changes. You may go ahead and discard all changes, or close this window and save them.', { resolve: 'Discard Changes', reject: 'Cancel' });
+  }
+
+
+  /**
+   * Returns Angular's path
+   * @param url
+   */
+  private extractPathname(url: string): string {
+      return url.slice(url.indexOf('#') + 1, url.indexOf('?') !== -1 ? url.indexOf('?') : url.length);
+  }
+
+}
diff --git a/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.directive.ts b/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.directive.ts
index bb7b613609..7e0399ffd1 100644
--- a/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.directive.ts
+++ b/dashboard/src/components/widget/edit-mode-overlay/che-edit-mode-overlay.directive.ts
@@ -17,25 +17,67 @@ export interface ICheEditModeOverlayMessage {
 }
 
 export interface ICheEditModeOverlayButton {
-  action: (args?: any) => void;
+  /**
+   * Listeners to be called on button click.
+   */
+  action: (...args: any[]) => void;
+  /**
+   * Set field to `true` to disable button.
+   */
   disabled?: boolean;
+  /**
+   * Button name attribute value.
+   */
   name?: string;
+  /**
+   * Button title
+   */
   title?: string;
 }
 
 export interface ICheEditModeOverlayConfig {
+  /**
+   * Set field to `true` to show the overlay.
+   */
   visible?: boolean;
+  /**
+   * Set field to `true` to disable all buttons.
+   */
   disabled?: boolean;
+  /**
+   * A message to show.
+   */
   message?: ICheEditModeOverlayMessage;
+  /**
+   * "Save" button.
+   */
   saveButton?: ICheEditModeOverlayButton;
+  /**
+   * "Apply" button.
+   */
   applyButton?: ICheEditModeOverlayButton;
+  /**
+   * "Cancel" button.
+   */
   cancelButton?: ICheEditModeOverlayButton;
+  /**
+   * If `true` then user cannot leave the pages if there are unsaved changes.
+   */
+  preventPageLeave?: boolean;
+  /**
+   * Listener to be called on `$locationChangeStart`.
+   */
+  onChangesDiscard?: () => ng.IPromise<void>;
 }
 
 export class CheEditModeOverlay implements ng.IDirective {
 
   restrict = 'E';
 
+  bindToController = true;
+  controller = 'CheEditModeOverlayController';
+  controllerAs = 'cheEditModeOverlayController';
+
   scope = {
     config: '='
   };
@@ -44,37 +86,37 @@ export class CheEditModeOverlay implements ng.IDirective {
     return `
 <div class="che-edit-mode-overlay"
      layout="row" layout-align="center center"
-     ng-if="config.visible">
+     ng-if="cheEditModeOverlayController.config.visible">
   <div class="che-edit-mode-overlay-message">
-    <span ng-if="config.message && config.message.content && config.message.visible"
-          ng-bind-html="config.message.content"></span>
+    <span ng-if="cheEditModeOverlayController.config.message && cheEditModeOverlayController.config.message.content && cheEditModeOverlayController.config.message.visible"
+          ng-bind-html="cheEditModeOverlayController.config.message.content"></span>
   </div>
 
   <!-- 'Save' button -->
-  <div ng-if="config.saveButton">
-    <che-button-save-flat che-button-title="{{config.saveButton.title || 'Save'}}"
+  <div ng-if="cheEditModeOverlayController.config.saveButton">
+    <che-button-save-flat che-button-title="{{cheEditModeOverlayController.config.saveButton.title || 'Save'}}"
                           class="save-button"
-                          name="{{config.saveButton.name || 'save-button'}}"
-                          ng-disabled="config.disabled || config.saveButton.disabled"
-                          ng-click="config.saveButton.action()"></che-button-save-flat>
+                          name="{{cheEditModeOverlayController.config.saveButton.name || 'save-button'}}"
+                          ng-disabled="cheEditModeOverlayController.config.disabled || cheEditModeOverlayController.config.saveButton.disabled"
+                          ng-click="cheEditModeOverlayController.config.saveButton.action()"></che-button-save-flat>
   </div>
 
   <!-- 'Apply' button -->
-  <div ng-if="config.applyButton">
-    <che-button-save-flat che-button-title="{{config.applyButton.title || 'Apply'}}"
-                          name="{{config.applyButton.name || 'apply-button'}}"
+  <div ng-if="cheEditModeOverlayController.config.applyButton">
+    <che-button-save-flat che-button-title="{{cheEditModeOverlayController.config.applyButton.title || 'Apply'}}"
+                          name="{{cheEditModeOverlayController.config.applyButton.name || 'apply-button'}}"
                           class="apply-button"
-                          ng-disabled="config.disabled || config.applyButton.disabled"
-                          ng-click="config.applyButton.action()"></che-button-save-flat>
+                          ng-disabled="cheEditModeOverlayController.config.disabled || cheEditModeOverlayController.config.applyButton.disabled"
+                          ng-click="cheEditModeOverlayController.config.applyButton.action()"></che-button-save-flat>
   </div>
 
   <!-- 'Cancel' button -->
-  <div ng-if="config.cancelButton">
-    <che-button-cancel-flat che-button-title="{{config.cancelButton.title || 'Cancel'}}"
-                            name="{{config.cancelButton.name || 'cancel-button'}}"
+  <div ng-if="cheEditModeOverlayController.config.cancelButton">
+    <che-button-cancel-flat che-button-title="{{cheEditModeOverlayController.config.cancelButton.title || 'Cancel'}}"
+                            name="{{cheEditModeOverlayController.config.cancelButton.name || 'cancel-button'}}"
                             class="cancel-button"
-                            ng-disabled="config.cancelButton.disabled"
-                            ng-click="config.cancelButton.action()"></che-button-cancel-flat>
+                            ng-disabled="cheEditModeOverlayController.config.cancelButton.disabled"
+                            ng-click="cheEditModeOverlayController.config.cancelButton.action()"></che-button-cancel-flat>
   </div>
 
 </div>`;
diff --git a/dashboard/src/components/widget/widget-config.ts b/dashboard/src/components/widget/widget-config.ts
index fa984b79c3..86a603d087 100644
--- a/dashboard/src/components/widget/widget-config.ts
+++ b/dashboard/src/components/widget/widget-config.ts
@@ -91,6 +91,7 @@ import {CheEditorController} from './editor/che-editor.controller';
 import {PagingButtons} from './paging-button/paging-button.directive';
 import {CheRowToolbar} from './toolbar/che-row-toolbar.directive';
 import {CheEditModeOverlay} from './edit-mode-overlay/che-edit-mode-overlay.directive';
+import { CheEditModeOverlayController } from './edit-mode-overlay/che-edit-mode-overlay.controller';
 
 export class WidgetConfig {
 
@@ -204,6 +205,7 @@ export class WidgetConfig {
     register.directive('cheTogglePopover', CheTogglePopover);
     register.directive('toggleButtonPopover', CheToggleButtonPopover);
     // edit overlay
+    register.controller('CheEditModeOverlayController', CheEditModeOverlayController);
     register.directive('cheEditModeOverlay', CheEditModeOverlay);
   }
 }
-- 
GitLab