thingsboard-aplcache

Changes

ui/src/app/dashboard/assign-to-customer.controller.js 123(+0 -123)

Details

diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.cql b/application/src/main/data/upgrade/1.4.0/schema_update.cql
index c5b656f..7a63bb3 100644
--- a/application/src/main/data/upgrade/1.4.0/schema_update.cql
+++ b/application/src/main/data/upgrade/1.4.0/schema_update.cql
@@ -87,3 +87,26 @@ CREATE TABLE IF NOT EXISTS thingsboard.audit_log_by_tenant_id_partitions (
   PRIMARY KEY (( tenant_id ), partition)
 ) WITH CLUSTERING ORDER BY ( partition ASC )
 AND compaction = { 'class' :  'LeveledCompactionStrategy'  };
+
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_tenant_and_search_text;
+DROP MATERIALIZED VIEW IF EXISTS thingsboard.dashboard_by_customer_and_search_text;
+
+DROP TABLE IF EXISTS thingsboard.dashboard;
+
+CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
+	id timeuuid,
+	tenant_id timeuuid,
+	title text,
+	search_text text,
+	assigned_customers text,
+	configuration text,
+	PRIMARY KEY (id, tenant_id)
+);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
+	SELECT *
+	from thingsboard.dashboard
+	WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( tenant_id, search_text, id )
+	WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
+
diff --git a/application/src/main/data/upgrade/1.4.0/schema_update.sql b/application/src/main/data/upgrade/1.4.0/schema_update.sql
index b2e3a97..c635414 100644
--- a/application/src/main/data/upgrade/1.4.0/schema_update.sql
+++ b/application/src/main/data/upgrade/1.4.0/schema_update.sql
@@ -29,3 +29,13 @@ CREATE TABLE IF NOT EXISTS audit_log (
     action_failure_details varchar(1000000)
 );
 
+DROP TABLE IF EXISTS dashboard;
+
+CREATE TABLE IF NOT EXISTS dashboard (
+    id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
+    configuration varchar(10000000),
+    assigned_customers varchar(1000000),
+    search_text varchar(255),
+    tenant_id varchar(31),
+    title varchar(255)
+);
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 28489ca..c78ad18 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -423,7 +423,7 @@ public abstract class BaseController {
         try {
             validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
             Dashboard dashboard = dashboardService.findDashboardById(dashboardId);
-            checkDashboard(dashboard, true);
+            checkDashboard(dashboard);
             return dashboard;
         } catch (Exception e) {
             throw handleException(e, false);
@@ -434,28 +434,23 @@ public abstract class BaseController {
         try {
             validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
             DashboardInfo dashboardInfo = dashboardService.findDashboardInfoById(dashboardId);
-            SecurityUser authUser = getCurrentUser();
-            checkDashboard(dashboardInfo, authUser.getAuthority() != Authority.SYS_ADMIN);
+            checkDashboard(dashboardInfo);
             return dashboardInfo;
         } catch (Exception e) {
             throw handleException(e, false);
         }
     }
 
-    private void checkDashboard(DashboardInfo dashboard, boolean checkCustomerId) throws ThingsboardException {
+    private void checkDashboard(DashboardInfo dashboard) throws ThingsboardException {
         checkNotNull(dashboard);
         checkTenantId(dashboard.getTenantId());
         SecurityUser authUser = getCurrentUser();
         if (authUser.getAuthority() == Authority.CUSTOMER_USER) {
-            if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
+            if (!dashboard.isAssignedToCustomer(authUser.getCustomerId())) {
                 throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
                         ThingsboardErrorCode.PERMISSION_DENIED);
             }
         }
-        if (checkCustomerId &&
-                dashboard.getCustomerId() != null && !dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
-            checkCustomerId(dashboard.getCustomerId());
-        }
     }
 
     ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
index 34320b1..a4664a5 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -18,20 +18,22 @@ package org.thingsboard.server.controller;
 import org.springframework.http.HttpStatus;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Dashboard;
-import org.thingsboard.server.common.data.DashboardInfo;
-import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.*;
 import org.thingsboard.server.common.data.audit.ActionType;
 import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.dao.exception.IncorrectParameterException;
 import org.thingsboard.server.dao.model.ModelConstants;
 import org.thingsboard.server.exception.ThingsboardException;
 
+import java.util.HashSet;
+import java.util.Set;
+
 @RestController
 @RequestMapping("/api")
 public class DashboardController extends BaseController {
@@ -80,7 +82,7 @@ public class DashboardController extends BaseController {
             Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
 
             logEntityAction(savedDashboard.getId(), savedDashboard,
-                    savedDashboard.getCustomerId(),
+                    null,
                     dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
 
             return savedDashboard;
@@ -103,7 +105,7 @@ public class DashboardController extends BaseController {
             dashboardService.deleteDashboard(dashboardId);
 
             logEntityAction(dashboardId, dashboard,
-                    dashboard.getCustomerId(),
+                    null,
                     ActionType.DELETED, null, strDashboardId);
 
         } catch (Exception e) {
@@ -134,7 +136,7 @@ public class DashboardController extends BaseController {
             Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
 
             logEntityAction(dashboardId, savedDashboard,
-                    savedDashboard.getCustomerId(),
+                    customerId,
                     ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
 
 
@@ -150,23 +152,22 @@ public class DashboardController extends BaseController {
     }
 
     @PreAuthorize("hasAuthority('TENANT_ADMIN')")
-    @RequestMapping(value = "/customer/dashboard/{dashboardId}", method = RequestMethod.DELETE)
+    @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
     @ResponseBody 
-    public Dashboard unassignDashboardFromCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
+    public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
+                                                   @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
+        checkParameter("customerId", strCustomerId);
         checkParameter(DASHBOARD_ID, strDashboardId);
         try {
+            CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+            Customer customer = checkCustomerId(customerId);
             DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
             Dashboard dashboard = checkDashboardId(dashboardId);
-            if (dashboard.getCustomerId() == null || dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
-                throw new IncorrectParameterException("Dashboard isn't assigned to any customer!");
-            }
 
-            Customer customer = checkCustomerId(dashboard.getCustomerId());
-
-            Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId));
+            Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
 
             logEntityAction(dashboardId, dashboard,
-                    dashboard.getCustomerId(),
+                    customerId,
                     ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
 
             return savedDashboard;
@@ -181,6 +182,158 @@ public class DashboardController extends BaseController {
     }
 
     @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
+    @ResponseBody
+    public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
+                                              @RequestBody String[] strCustomerIds) throws ThingsboardException {
+        checkParameter(DASHBOARD_ID, strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            Dashboard dashboard = checkDashboardId(dashboardId);
+
+            Set<CustomerId> customerIds = new HashSet<>();
+            if (strCustomerIds != null) {
+                for (String strCustomerId : strCustomerIds) {
+                    customerIds.add(new CustomerId(toUUID(strCustomerId)));
+                }
+            }
+
+            Set<CustomerId> addedCustomerIds = new HashSet<>();
+            Set<CustomerId> removedCustomerIds = new HashSet<>();
+            for (CustomerId customerId : customerIds) {
+                if (!dashboard.isAssignedToCustomer(customerId)) {
+                    addedCustomerIds.add(customerId);
+                }
+            }
+
+            Set<ShortCustomerInfo> assignedCustomers = dashboard.getAssignedCustomers();
+            if (assignedCustomers != null) {
+                for (ShortCustomerInfo customerInfo : assignedCustomers) {
+                    if (!customerIds.contains(customerInfo.getCustomerId())) {
+                        removedCustomerIds.add(customerInfo.getCustomerId());
+                    }
+                }
+            }
+
+            if (addedCustomerIds.isEmpty() && removedCustomerIds.isEmpty()) {
+                return dashboard;
+            } else {
+                Dashboard savedDashboard = null;
+                for (CustomerId customerId : addedCustomerIds) {
+                    savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
+                    ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
+                    logEntityAction(dashboardId, savedDashboard,
+                            customerId,
+                            ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
+                }
+                for (CustomerId customerId : removedCustomerIds) {
+                    ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
+                    savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
+                    logEntityAction(dashboardId, dashboard,
+                            customerId,
+                            ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
+
+                }
+                return savedDashboard;
+            }
+        } catch (Exception e) {
+
+            logEntityAction(emptyId(EntityType.DASHBOARD), null,
+                    null,
+                    ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
+
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
+    @ResponseBody
+    public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
+                                           @RequestBody String[] strCustomerIds) throws ThingsboardException {
+        checkParameter(DASHBOARD_ID, strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            Dashboard dashboard = checkDashboardId(dashboardId);
+
+            Set<CustomerId> customerIds = new HashSet<>();
+            if (strCustomerIds != null) {
+                for (String strCustomerId : strCustomerIds) {
+                    CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+                    if (!dashboard.isAssignedToCustomer(customerId)) {
+                        customerIds.add(customerId);
+                    }
+                }
+            }
+
+            if (customerIds.isEmpty()) {
+                return dashboard;
+            } else {
+                Dashboard savedDashboard = null;
+                for (CustomerId customerId : customerIds) {
+                    savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, customerId));
+                    ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId);
+                    logEntityAction(dashboardId, savedDashboard,
+                            customerId,
+                            ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
+                }
+                return savedDashboard;
+            }
+        } catch (Exception e) {
+
+            logEntityAction(emptyId(EntityType.DASHBOARD), null,
+                    null,
+                    ActionType.ASSIGNED_TO_CUSTOMER, e, strDashboardId);
+
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
+    @ResponseBody
+    public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
+                                              @RequestBody String[] strCustomerIds) throws ThingsboardException {
+        checkParameter(DASHBOARD_ID, strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            Dashboard dashboard = checkDashboardId(dashboardId);
+
+            Set<CustomerId> customerIds = new HashSet<>();
+            if (strCustomerIds != null) {
+                for (String strCustomerId : strCustomerIds) {
+                    CustomerId customerId = new CustomerId(toUUID(strCustomerId));
+                    if (dashboard.isAssignedToCustomer(customerId)) {
+                        customerIds.add(customerId);
+                    }
+                }
+            }
+
+            if (customerIds.isEmpty()) {
+                return dashboard;
+            } else {
+                Dashboard savedDashboard = null;
+                for (CustomerId customerId : customerIds) {
+                    ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
+                    savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, customerId));
+                    logEntityAction(dashboardId, dashboard,
+                            customerId,
+                            ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
+
+                }
+                return savedDashboard;
+            }
+        } catch (Exception e) {
+
+            logEntityAction(emptyId(EntityType.DASHBOARD), null,
+                    null,
+                    ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
+
+            throw handleException(e);
+        }
+    }
+
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
     @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
     @ResponseBody
     public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
@@ -192,7 +345,7 @@ public class DashboardController extends BaseController {
             Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
 
             logEntityAction(dashboardId, savedDashboard,
-                    savedDashboard.getCustomerId(),
+                    publicCustomer.getId(),
                     ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
 
             return savedDashboard;
@@ -206,6 +359,33 @@ public class DashboardController extends BaseController {
         }
     }
 
+    @PreAuthorize("hasAuthority('TENANT_ADMIN')")
+    @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
+    @ResponseBody
+    public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
+        checkParameter(DASHBOARD_ID, strDashboardId);
+        try {
+            DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
+            Dashboard dashboard = checkDashboardId(dashboardId);
+            Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
+
+            Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(dashboardId, publicCustomer.getId()));
+
+            logEntityAction(dashboardId, dashboard,
+                    publicCustomer.getId(),
+                    ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, publicCustomer.getId().toString(), publicCustomer.getName());
+
+            return savedDashboard;
+        } catch (Exception e) {
+
+            logEntityAction(emptyId(EntityType.DASHBOARD), null,
+                    null,
+                    ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDashboardId);
+
+            throw handleException(e);
+        }
+    }
+
     @PreAuthorize("hasAuthority('SYS_ADMIN')")
     @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
     @ResponseBody
@@ -245,19 +425,20 @@ public class DashboardController extends BaseController {
     @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
     @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
     @ResponseBody
-    public TextPageData<DashboardInfo> getCustomerDashboards(
+    public TimePageData<DashboardInfo> getCustomerDashboards(
             @PathVariable("customerId") String strCustomerId,
             @RequestParam int limit,
-            @RequestParam(required = false) String textSearch,
-            @RequestParam(required = false) String idOffset,
-            @RequestParam(required = false) String textOffset) throws ThingsboardException {
+            @RequestParam(required = false) Long startTime,
+            @RequestParam(required = false) Long endTime,
+            @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
+            @RequestParam(required = false) String offset) throws ThingsboardException {
         checkParameter("customerId", strCustomerId);
         try {
             TenantId tenantId = getCurrentUser().getTenantId();
             CustomerId customerId = new CustomerId(toUUID(strCustomerId));
             checkCustomerId(customerId);
-            TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
-            return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+            TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
+            return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get());
         } catch (Exception e) {
             throw handleException(e);
         }
diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
index dacf453..0b2c808 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
@@ -24,6 +24,7 @@ import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Service;
 import org.thingsboard.server.dao.cassandra.CassandraCluster;
 import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
+import org.thingsboard.server.dao.dashboard.DashboardService;
 import org.thingsboard.server.dao.util.NoSqlDao;
 import org.thingsboard.server.service.install.cql.CQLStatementsParser;
 import org.thingsboard.server.service.install.cql.CassandraDbHelper;
@@ -33,6 +34,8 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.List;
 
+import static org.thingsboard.server.service.install.DatabaseHelper.*;
+
 @Service
 @NoSqlDao
 @Profile("install")
@@ -40,12 +43,6 @@ import java.util.List;
 public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
 
     private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
-    public static final String DEVICE = "device";
-    public static final String TENANT_ID = "tenant_id";
-    public static final String CUSTOMER_ID = "customer_id";
-    public static final String SEARCH_TEXT = "search_text";
-    public static final String ADDITIONAL_INFO = "additional_info";
-    public static final String ASSET = "asset";
 
     @Value("${install.data_dir}")
     private String dataDir;
@@ -56,6 +53,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
     @Autowired
     private CassandraInstallCluster installCluster;
 
+    @Autowired
+    private DashboardService dashboardService;
+
     @Override
     public void upgradeDatabase(String fromVersion) throws Exception {
 
@@ -160,10 +160,32 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
             case "1.3.0":
                 break;
             case "1.3.1":
+
+                cluster.getSession();
+
+                ks = cluster.getCluster().getMetadata().getKeyspace(cluster.getKeyspaceName());
+
+                log.info("Dumping dashboards ...");
+                Path dashboardsDump = CassandraDbHelper.dumpCfIfExists(ks, cluster.getSession(), DASHBOARD,
+                        new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
+                        new String[]{"", "", "", "", "", "", ""},
+                        "tb-dashboards", true);
+                log.info("Dashboards dumped.");
+
+
                 log.info("Updating schema ...");
                 schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
                 loadCql(schemaUpdateFile);
                 log.info("Schema updated.");
+
+                log.info("Restoring dashboards ...");
+                if (dashboardsDump != null) {
+                    CassandraDbHelper.loadCf(ks, cluster.getSession(), DASHBOARD,
+                            new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true);
+                    DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, false);
+                    Files.deleteIfExists(dashboardsDump);
+                }
+                log.info("Dashboards restored.");
                 break;
             default:
                 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
diff --git a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
index 0a411f6..dae9bb6 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/cql/CassandraDbHelper.java
@@ -28,16 +28,25 @@ import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
 import java.util.*;
 
-public class CassandraDbHelper {
+import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
 
-    private static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
+public class CassandraDbHelper {
 
     public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
                                       String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
+        return dumpCfIfExists(ks, session, cfName, columns, defaultValues, dumpPrefix, false);
+    }
+
+    public static Path dumpCfIfExists(KeyspaceMetadata ks, Session session, String cfName,
+                                      String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception {
         if (ks.getTable(cfName) != null) {
             Path dumpFile = Files.createTempFile(dumpPrefix, null);
             Files.deleteIfExists(dumpFile);
-            try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), CSV_DUMP_FORMAT)) {
+            CSVFormat csvFormat = CSV_DUMP_FORMAT;
+            if (printHeader) {
+                csvFormat = csvFormat.withHeader(columns);
+            }
+            try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) {
                 Statement stmt = new SimpleStatement("SELECT * FROM " + cfName);
                 stmt.setFetchSize(1000);
                 ResultSet rs = session.execute(stmt);
@@ -75,9 +84,19 @@ public class CassandraDbHelper {
     }
 
     public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile) throws Exception {
+        loadCf(ks, session, cfName, columns, sourceFile, false);
+    }
+
+    public static void loadCf(KeyspaceMetadata ks, Session session, String cfName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception {
         TableMetadata tableMetadata = ks.getTable(cfName);
         PreparedStatement prepared = session.prepare(createInsertStatement(cfName, columns));
-        try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), CSV_DUMP_FORMAT.withHeader(columns))) {
+        CSVFormat csvFormat = CSV_DUMP_FORMAT;
+        if (parseHeader) {
+            csvFormat = csvFormat.withFirstRecordAsHeader();
+        } else {
+            csvFormat = CSV_DUMP_FORMAT.withHeader(columns);
+        }
+        try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) {
             csvParser.forEach(record -> {
                 BoundStatement boundStatement = prepared.bind();
                 for (String column : columns) {
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
new file mode 100644
index 0000000..fa0dd2f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
@@ -0,0 +1,100 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.install;
+
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.ShortCustomerInfo;
+import org.thingsboard.server.common.data.UUIDConverter;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DashboardId;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+/**
+ * Created by igor on 2/27/18.
+ */
+@Slf4j
+public class DatabaseHelper {
+
+    public static final CSVFormat CSV_DUMP_FORMAT = CSVFormat.DEFAULT.withNullString("\\N");
+
+    public static final String DEVICE = "device";
+    public static final String TENANT_ID = "tenant_id";
+    public static final String CUSTOMER_ID = "customer_id";
+    public static final String SEARCH_TEXT = "search_text";
+    public static final String ADDITIONAL_INFO = "additional_info";
+    public static final String ASSET = "asset";
+    public static final String DASHBOARD = "dashboard";
+    public static final String ID = "id";
+    public static final String TITLE = "title";
+    public static final String ASSIGNED_CUSTOMERS = "assigned_customers";
+    public static final String CONFIGURATION = "configuration";
+
+    public static final ObjectMapper objectMapper = new ObjectMapper();
+
+    public static void upgradeTo40_assignDashboards(Path dashboardsDump, DashboardService dashboardService, boolean sql) throws Exception {
+        JavaType assignedCustomersType =
+                objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
+        try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(dashboardsDump), CSV_DUMP_FORMAT.withFirstRecordAsHeader())) {
+            csvParser.forEach(record -> {
+                String customerIdString = record.get(CUSTOMER_ID);
+                String assignedCustomersString = record.get(ASSIGNED_CUSTOMERS);
+                DashboardId dashboardId = new DashboardId(toUUID(record.get(ID), sql));
+                List<CustomerId> customerIds = new ArrayList<>();
+                if (!StringUtils.isEmpty(assignedCustomersString)) {
+                    try {
+                        Set<ShortCustomerInfo> assignedCustomers = objectMapper.readValue(assignedCustomersString, assignedCustomersType);
+                        assignedCustomers.forEach((customerInfo) -> {
+                            CustomerId customerId = customerInfo.getCustomerId();
+                            if (!customerId.isNullUid()) {
+                                customerIds.add(customerId);
+                            }
+                        });
+                    } catch (IOException e) {
+                        log.error("Unable to parse assigned customers field", e);
+                    }
+                }
+                if (!StringUtils.isEmpty(customerIdString)) {
+                    CustomerId customerId = new CustomerId(toUUID(customerIdString, sql));
+                    if (!customerId.isNullUid()) {
+                        customerIds.add(customerId);
+                    }
+                }
+                for (CustomerId customerId : customerIds) {
+                    dashboardService.assignDashboardToCustomer(dashboardId, customerId);
+                }
+            });
+        }
+    }
+
+    private static UUID toUUID(String src, boolean sql) {
+        if (sql) {
+            return UUIDConverter.fromString(src);
+        } else {
+            return UUID.fromString(src);
+        }
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
index 4e81f94..a102dcb 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
@@ -339,8 +339,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
                             JsonNode dashboardJson = objectMapper.readTree(path.toFile());
                             Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
                             dashboard.setTenantId(tenantId);
-                            dashboard.setCustomerId(customerId);
-                            dashboardService.saveDashboard(dashboard);
+                            Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
+                            if (customerId != null && !customerId.isNullUid()) {
+                                dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
+                            }
                         } catch (Exception e) {
                             log.error("Unable to load dashboard from json: [{}]", path.toString());
                             throw new RuntimeException("Unable to load dashboard from json", e);
diff --git a/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java
new file mode 100644
index 0000000..669646f
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/install/sql/SqlDbHelper.java
@@ -0,0 +1,171 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.install.sql;
+
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.csv.CSVRecord;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.sql.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.thingsboard.server.service.install.DatabaseHelper.CSV_DUMP_FORMAT;
+
+/**
+ * Created by igor on 2/27/18.
+ */
+@Slf4j
+public class SqlDbHelper {
+
+    public static Path dumpTableIfExists(Connection conn, String tableName,
+                                         String[] columns, String[] defaultValues, String dumpPrefix) throws Exception {
+        return dumpTableIfExists(conn, tableName, columns, defaultValues, dumpPrefix, false);
+    }
+
+    public static Path dumpTableIfExists(Connection conn, String tableName,
+                                         String[] columns, String[] defaultValues, String dumpPrefix, boolean printHeader) throws Exception {
+
+        if (tableExists(conn, tableName)) {
+            Path dumpFile = Files.createTempFile(dumpPrefix, null);
+            Files.deleteIfExists(dumpFile);
+            CSVFormat csvFormat = CSV_DUMP_FORMAT;
+            if (printHeader) {
+                csvFormat = csvFormat.withHeader(columns);
+            }
+            try (CSVPrinter csvPrinter = new CSVPrinter(Files.newBufferedWriter(dumpFile), csvFormat)) {
+                try (PreparedStatement stmt = conn.prepareStatement("SELECT * FROM " + tableName)) {
+                    try (ResultSet tableRes = stmt.executeQuery()) {
+                        ResultSetMetaData resMetaData = tableRes.getMetaData();
+                        Map<String, Integer> columnIndexMap = new HashMap<>();
+                        for (int i = 1; i <= resMetaData.getColumnCount(); i++) {
+                            String columnName = resMetaData.getColumnName(i);
+                            columnIndexMap.put(columnName.toUpperCase(), i);
+                        }
+                        while(tableRes.next()) {
+                            dumpRow(tableRes, columnIndexMap, columns, defaultValues, csvPrinter);
+                        }
+                    }
+                }
+            }
+            return dumpFile;
+        } else {
+            return null;
+        }
+    }
+
+    private static boolean tableExists(Connection conn, String tableName) {
+        try (Statement stmt = conn.createStatement()) {
+            stmt.executeQuery("select * from " + tableName + " where 1=0");
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile) throws Exception {
+        loadTable(conn, tableName, columns, sourceFile, false);
+    }
+
+    public static void loadTable(Connection conn, String tableName, String[] columns, Path sourceFile, boolean parseHeader) throws Exception {
+        CSVFormat csvFormat = CSV_DUMP_FORMAT;
+        if (parseHeader) {
+            csvFormat = csvFormat.withFirstRecordAsHeader();
+        } else {
+            csvFormat = CSV_DUMP_FORMAT.withHeader(columns);
+        }
+        try (PreparedStatement prepared = conn.prepareStatement(createInsertStatement(tableName, columns))) {
+            try (CSVParser csvParser = new CSVParser(Files.newBufferedReader(sourceFile), csvFormat)) {
+                csvParser.forEach(record -> {
+                    try {
+                        for (int i = 0; i < columns.length; i++) {
+                            setColumnValue(i, columns[i], record, prepared);
+                        }
+                        prepared.execute();
+                    } catch (SQLException e) {
+                        log.error("Unable to load table record!", e);
+                    }
+                });
+            }
+        }
+    }
+
+    private static void dumpRow(ResultSet res, Map<String, Integer> columnIndexMap, String[] columns,
+                                String[] defaultValues, CSVPrinter csvPrinter) throws Exception {
+        List<String> record = new ArrayList<>();
+        for (int i=0;i<columns.length;i++) {
+            String column = columns[i];
+            String defaultValue;
+            if (defaultValues != null && i < defaultValues.length) {
+                defaultValue = defaultValues[i];
+            } else {
+                defaultValue = "";
+            }
+            record.add(getColumnValue(column, defaultValue, columnIndexMap, res));
+        }
+        csvPrinter.printRecord(record);
+    }
+
+    private static String getColumnValue(String column, String defaultValue, Map<String, Integer> columnIndexMap, ResultSet res) {
+        int index = columnIndexMap.containsKey(column.toUpperCase()) ? columnIndexMap.get(column.toUpperCase()) : -1;
+        if (index > -1) {
+            String str;
+            try {
+                Object obj = res.getObject(index);
+                if (obj == null) {
+                    return null;
+                } else {
+                    str = obj.toString();
+                }
+            } catch (Exception e) {
+                str = "";
+            }
+            return str;
+        } else {
+            return defaultValue;
+        }
+    }
+
+    private static void setColumnValue(int index, String column,
+                                       CSVRecord record, PreparedStatement preparedStatement) throws SQLException {
+        String value = record.get(column);
+        int type = preparedStatement.getParameterMetaData().getParameterType(index + 1);
+        preparedStatement.setObject(index + 1, value, type);
+    }
+
+    private static String createInsertStatement(String tableName, String[] columns) {
+        StringBuilder insertStatementBuilder = new StringBuilder();
+        insertStatementBuilder.append("INSERT INTO ").append(tableName).append(" (");
+        for (String column : columns) {
+            insertStatementBuilder.append(column).append(",");
+        }
+        insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
+        insertStatementBuilder.append(") VALUES (");
+        for (String column : columns) {
+            insertStatementBuilder.append("?").append(",");
+        }
+        insertStatementBuilder.deleteCharAt(insertStatementBuilder.length() - 1);
+        insertStatementBuilder.append(")");
+        return insertStatementBuilder.toString();
+    }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index 098b4fe..3d73ffc 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -17,18 +17,26 @@
 package org.thingsboard.server.service.install;
 
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Profile;
 import org.springframework.stereotype.Service;
+import org.thingsboard.server.dao.dashboard.DashboardService;
 import org.thingsboard.server.dao.util.SqlDao;
+import org.thingsboard.server.service.install.cql.CassandraDbHelper;
+import org.thingsboard.server.service.install.sql.SqlDbHelper;
 
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.sql.Connection;
+import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
 
+import static org.thingsboard.server.service.install.DatabaseHelper.*;
+import static org.thingsboard.server.service.install.DatabaseHelper.CONFIGURATION;
+
 @Service
 @Profile("install")
 @Slf4j
@@ -49,6 +57,9 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
     @Value("${spring.datasource.password}")
     private String dbPassword;
 
+    @Autowired
+    private DashboardService dashboardService;
+
     @Override
     public void upgradeDatabase(String fromVersion) throws Exception {
         switch (fromVersion) {
@@ -62,13 +73,30 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
                 log.info("Schema updated.");
                 break;
             case "1.3.1":
-                log.info("Updating schema ...");
-                schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
                 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
+
+                    log.info("Dumping dashboards ...");
+                    Path dashboardsDump = SqlDbHelper.dumpTableIfExists(conn, DASHBOARD,
+                            new String[]{ID, TENANT_ID, CUSTOMER_ID, TITLE, SEARCH_TEXT, ASSIGNED_CUSTOMERS, CONFIGURATION},
+                            new String[]{"", "", "", "", "", "", ""},
+                            "tb-dashboards", true);
+                    log.info("Dashboards dumped.");
+
+                    log.info("Updating schema ...");
+                    schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
                     String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
                     conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
+                    log.info("Schema updated.");
+
+                    log.info("Restoring dashboards ...");
+                    if (dashboardsDump != null) {
+                        SqlDbHelper.loadTable(conn, DASHBOARD,
+                                new String[]{ID, TENANT_ID, TITLE, SEARCH_TEXT, CONFIGURATION}, dashboardsDump, true);
+                        DatabaseHelper.upgradeTo40_assignDashboards(dashboardsDump, dashboardService, true);
+                        Files.deleteIfExists(dashboardsDump);
+                    }
+                    log.info("Dashboards restored.");
                 }
-                log.info("Schema updated.");
                 break;
             default:
                 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
index bb06411..d73e7ba 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
@@ -19,6 +19,7 @@ import static org.hamcrest.Matchers.containsString;
 import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
 import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
+import java.sql.Time;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -29,6 +30,8 @@ import org.thingsboard.server.common.data.*;
 import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.common.data.security.Authority;
 import org.thingsboard.server.dao.model.ModelConstants;
 import org.junit.After;
@@ -82,8 +85,6 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
         Assert.assertNotNull(savedDashboard.getId());
         Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
         Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId());
-        Assert.assertNotNull(savedDashboard.getCustomerId());
-        Assert.assertEquals(NULL_UUID, savedDashboard.getCustomerId().getId());
         Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
         
         savedDashboard.setTitle("My new dashboard");
@@ -136,17 +137,20 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
         
         Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() 
                 + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
-        Assert.assertEquals(savedCustomer.getId(), assignedDashboard.getCustomerId());
-        
+
+        Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
+
         Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
-        Assert.assertEquals(savedCustomer.getId(), foundDashboard.getCustomerId());
+        Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo()));
 
         Dashboard unassignedDashboard = 
-                doDelete("/api/customer/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
-        Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDashboard.getCustomerId().getId());
-        
+                doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
+
+        Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty());
+
         foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
-        Assert.assertEquals(ModelConstants.NULL_UUID, foundDashboard.getCustomerId().getId());
+
+        Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty());
     }
     
     @Test
@@ -320,11 +324,11 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
         }
         
         List<DashboardInfo> loadedDashboards = new ArrayList<>();
-        TextPageLink pageLink = new TextPageLink(21);
-        TextPageData<DashboardInfo> pageData = null;
+        TimePageLink pageLink = new TimePageLink(21);
+        TimePageData<DashboardInfo> pageData = null;
         do {
-            pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", 
-                    new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
+            pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
+                    new TypeReference<TimePageData<DashboardInfo>>(){}, pageLink);
             loadedDashboards.addAll(pageData.getData());
             if (pageData.hasNext()) {
                 pageLink = pageData.getNextPageLink();
@@ -336,93 +340,5 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
         
         Assert.assertEquals(dashboards, loadedDashboards);
     }
-    
-    @Test
-    public void testFindCustomerDashboardsByTitle() throws Exception {
-        Customer customer = new Customer();
-        customer.setTitle("Test customer");
-        customer = doPost("/api/customer", customer, Customer.class);
-        CustomerId customerId = customer.getId();
-
-        String title1 = "Dashboard title 1";
-        List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
-        for (int i=0;i<125;i++) {
-            Dashboard dashboard = new Dashboard();
-            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
-            String title = title1+suffix;
-            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
-            dashboard.setTitle(title);
-            dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
-            dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
-                    + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
-        }
-        String title2 = "Dashboard title 2";
-        List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
-        for (int i=0;i<143;i++) {
-            Dashboard dashboard = new Dashboard();
-            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
-            String title = title2+suffix;
-            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
-            dashboard.setTitle(title);
-            dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
-            dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
-                    + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
-        }
-        
-        List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
-        TextPageLink pageLink = new TextPageLink(18, title1);
-        TextPageData<DashboardInfo> pageData = null;
-        do {
-            pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", 
-                    new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
-            loadedDashboardsTitle1.addAll(pageData.getData());
-            if (pageData.hasNext()) {
-                pageLink = pageData.getNextPageLink();
-            }
-        } while (pageData.hasNext());
-        
-        Collections.sort(dashboardsTitle1, idComparator);
-        Collections.sort(loadedDashboardsTitle1, idComparator);
-        
-        Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
-        
-        List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
-        pageLink = new TextPageLink(7, title2);
-        do {
-            pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", 
-                    new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
-            loadedDashboardsTitle2.addAll(pageData.getData());
-            if (pageData.hasNext()) {
-                pageLink = pageData.getNextPageLink();
-            }
-        } while (pageData.hasNext());
-
-        Collections.sort(dashboardsTitle2, idComparator);
-        Collections.sort(loadedDashboardsTitle2, idComparator);
-        
-        Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
-        
-        for (DashboardInfo dashboard : loadedDashboardsTitle1) {
-            doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
-            .andExpect(status().isOk());
-        }
-        
-        pageLink = new TextPageLink(5, title1);
-        pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", 
-                new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
-        Assert.assertFalse(pageData.hasNext());
-        Assert.assertEquals(0, pageData.getData().size());
-        
-        for (DashboardInfo dashboard : loadedDashboardsTitle2) {
-            doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
-            .andExpect(status().isOk());
-        }
-        
-        pageLink = new TextPageLink(9, title2);
-        pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", 
-                new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
-        Assert.assertFalse(pageData.hasNext());
-        Assert.assertEquals(0, pageData.getData().size());
-    }
 
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
index 1ee9cab..59431f3 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
@@ -69,6 +69,11 @@ public class Customer extends ContactBased<CustomerId> implements HasName {
         return false;
     }
 
+    @JsonIgnore
+    public ShortCustomerInfo toShortCustomerInfo() {
+        return new ShortCustomerInfo(id, title, isPublic());
+    }
+
     @Override
     @JsonProperty(access = Access.READ_ONLY)
     public String getName() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
index 71f2b13..fa9f048 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
@@ -79,8 +79,6 @@ public class Dashboard extends DashboardInfo {
         StringBuilder builder = new StringBuilder();
         builder.append("Dashboard [tenantId=");
         builder.append(getTenantId());
-        builder.append(", customerId=");
-        builder.append(getCustomerId());
         builder.append(", title=");
         builder.append(getTitle());
         builder.append(", configuration=");
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
index 15898c6..65a467d 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
@@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 
+import java.util.*;
+
 public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName {
 
     private TenantId tenantId;
-    private CustomerId customerId;
     private String title;
+    private Set<ShortCustomerInfo> assignedCustomers;
 
     public DashboardInfo() {
         super();
@@ -37,8 +39,8 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
     public DashboardInfo(DashboardInfo dashboardInfo) {
         super(dashboardInfo);
         this.tenantId = dashboardInfo.getTenantId();
-        this.customerId = dashboardInfo.getCustomerId();
         this.title = dashboardInfo.getTitle();
+        this.assignedCustomers = dashboardInfo.getAssignedCustomers();
     }
 
     public TenantId getTenantId() {
@@ -49,14 +51,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
         this.tenantId = tenantId;
     }
 
-    public CustomerId getCustomerId() {
-        return customerId;
-    }
-
-    public void setCustomerId(CustomerId customerId) {
-        this.customerId = customerId;
-    }
-
     public String getTitle() {
         return title;
     }
@@ -65,6 +59,62 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
         this.title = title;
     }
 
+    public Set<ShortCustomerInfo> getAssignedCustomers() {
+        return assignedCustomers;
+    }
+
+    public void setAssignedCustomers(Set<ShortCustomerInfo> assignedCustomers) {
+        this.assignedCustomers = assignedCustomers;
+    }
+
+    public boolean isAssignedToCustomer(CustomerId customerId) {
+        return this.assignedCustomers != null && this.assignedCustomers.contains(new ShortCustomerInfo(customerId, null, false));
+    }
+
+    public ShortCustomerInfo getAssignedCustomerInfo(CustomerId customerId) {
+        if (this.assignedCustomers != null) {
+            for (ShortCustomerInfo customerInfo : this.assignedCustomers) {
+                if (customerInfo.getCustomerId().equals(customerId)) {
+                    return customerInfo;
+                }
+            }
+        }
+        return null;
+    }
+
+    public boolean addAssignedCustomer(Customer customer) {
+        ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
+        if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
+            return false;
+        } else {
+            if (this.assignedCustomers == null) {
+                this.assignedCustomers = new HashSet<>();
+            }
+            this.assignedCustomers.add(customerInfo);
+            return true;
+        }
+    }
+
+    public boolean updateAssignedCustomer(Customer customer) {
+        ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
+        if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
+            this.assignedCustomers.add(customerInfo);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public boolean removeAssignedCustomer(Customer customer) {
+        ShortCustomerInfo customerInfo = customer.toShortCustomerInfo();
+        if (this.assignedCustomers != null && this.assignedCustomers.contains(customerInfo)) {
+            this.assignedCustomers.remove(customerInfo);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     @Override
     @JsonProperty(access = JsonProperty.Access.READ_ONLY)
     public String getName() {
@@ -80,7 +130,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
     public int hashCode() {
         final int prime = 31;
         int result = super.hashCode();
-        result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
         result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
         result = prime * result + ((title == null) ? 0 : title.hashCode());
         return result;
@@ -95,11 +144,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
         if (getClass() != obj.getClass())
             return false;
         DashboardInfo other = (DashboardInfo) obj;
-        if (customerId == null) {
-            if (other.customerId != null)
-                return false;
-        } else if (!customerId.equals(other.customerId))
-            return false;
         if (tenantId == null) {
             if (other.tenantId != null)
                 return false;
@@ -118,8 +162,6 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
         StringBuilder builder = new StringBuilder();
         builder.append("DashboardInfo [tenantId=");
         builder.append(tenantId);
-        builder.append(", customerId=");
-        builder.append(customerId);
         builder.append(", title=");
         builder.append(title);
         builder.append("]");
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
index 82798ab..7c9c5e6 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationTypeGroup.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.relation;
 public enum RelationTypeGroup {
 
     COMMON,
-    ALARM
+    ALARM,
+    DASHBOARD
 
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
new file mode 100644
index 0000000..68ca46a
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
@@ -0,0 +1,54 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import org.thingsboard.server.common.data.id.CustomerId;
+
+/**
+ * Created by igor on 2/27/18.
+ */
+
+@AllArgsConstructor
+public class ShortCustomerInfo {
+
+    @Getter @Setter
+    private CustomerId customerId;
+
+    @Getter @Setter
+    private String title;
+
+    @Getter @Setter
+    private boolean isPublic;
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ShortCustomerInfo that = (ShortCustomerInfo) o;
+
+        return customerId.equals(that.customerId);
+
+    }
+
+    @Override
+    public int hashCode() {
+        return customerId.hashCode();
+    }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
index f76d654..657bfba 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
@@ -97,7 +97,9 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
     public Customer saveCustomer(Customer customer) {
         log.trace("Executing saveCustomer [{}]", customer);
         customerValidator.validate(customer);
-        return customerDao.save(customer);
+        Customer savedCustomer = customerDao.save(customer);
+        dashboardService.updateCustomerDashboards(savedCustomer.getId());
+        return savedCustomer;
     }
 
     @Override
@@ -108,7 +110,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
         if (customer == null) {
             throw new IncorrectParameterException("Unable to delete non-existent customer.");
         }
-        dashboardService.unassignCustomerDashboards(customer.getTenantId(), customerId);
+        dashboardService.unassignCustomerDashboards(customerId);
         assetService.unassignCustomerAssets(customer.getTenantId(), customerId);
         deviceService.unassignCustomerDevices(customer.getTenantId(), customerId);
         userService.deleteCustomerUsers(customer.getTenantId(), customerId);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
index d0651a4..5f35913 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/CassandraDashboardInfoDao.java
@@ -15,16 +15,26 @@
  */
 package org.thingsboard.server.dao.dashboard;
 
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 import org.thingsboard.server.common.data.DashboardInfo;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 import org.thingsboard.server.dao.DaoUtil;
 import org.thingsboard.server.dao.model.nosql.DashboardInfoEntity;
 import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
+import org.thingsboard.server.dao.relation.RelationDao;
 import org.thingsboard.server.dao.util.NoSqlDao;
 
-import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
@@ -37,6 +47,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
 @NoSqlDao
 public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
 
+    @Autowired
+    private RelationDao relationDao;
+
     @Override
     protected Class<DashboardInfoEntity> getColumnFamilyClass() {
         return DashboardInfoEntity.class;
@@ -59,15 +72,18 @@ public class CassandraDashboardInfoDao extends CassandraAbstractSearchTextDao<Da
     }
 
     @Override
-    public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
+    public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
         log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
-        List<DashboardInfoEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
-                Arrays.asList(eq(DASHBOARD_CUSTOMER_ID_PROPERTY, customerId),
-                        eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)),
-                pageLink);
 
-        log.trace("Found dashboards [{}] by tenantId [{}], customerId [{}] and pageLink [{}]", dashboardEntities, tenantId, customerId, pageLink);
-        return DaoUtil.convertDataList(dashboardEntities);
+        ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
+
+        return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
+            List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
+            for (EntityRelation relation : input) {
+                dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
+            }
+            return Futures.successfulAsList(dashboardFutures);
+        });
     }
 
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
index a26bd14..baa020f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
@@ -15,8 +15,10 @@
  */
 package org.thingsboard.server.dao.dashboard;
 
+import com.google.common.util.concurrent.ListenableFuture;
 import org.thingsboard.server.common.data.DashboardInfo;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.dao.Dao;
 
 import java.util.List;
@@ -44,6 +46,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
      * @param pageLink the page link
      * @return the list of dashboard objects
      */
-    List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
+    ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink);
 
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
index 74d8544..06426e7 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
@@ -23,6 +23,10 @@ import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+
+import java.util.Set;
 
 public interface DashboardService {
     
@@ -38,7 +42,7 @@ public interface DashboardService {
 
     Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId);
 
-    Dashboard unassignDashboardFromCustomer(DashboardId dashboardId);
+    Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId);
 
     void deleteDashboard(DashboardId dashboardId);
 
@@ -46,8 +50,10 @@ public interface DashboardService {
 
     void deleteDashboardsByTenantId(TenantId tenantId);
 
-    TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+    ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink);
+
+    void unassignCustomerDashboards(CustomerId customerId);
 
-    void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);
+    void updateCustomerDashboards(CustomerId customerId);
 
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
index 53fdb73..741d313 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
@@ -15,30 +15,42 @@
  */
 package org.thingsboard.server.dao.dashboard;
 
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Dashboard;
-import org.thingsboard.server.common.data.DashboardInfo;
-import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.*;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
 import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 import org.thingsboard.server.dao.customer.CustomerDao;
 import org.thingsboard.server.dao.entity.AbstractEntityService;
 import org.thingsboard.server.dao.exception.DataValidationException;
 import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.relation.RelationDao;
 import org.thingsboard.server.dao.service.DataValidator;
 import org.thingsboard.server.dao.service.PaginatedRemover;
+import org.thingsboard.server.dao.service.TimePaginatedRemover;
 import org.thingsboard.server.dao.service.Validator;
 import org.thingsboard.server.dao.tenant.TenantDao;
 
+import javax.annotation.Nullable;
+import java.sql.Time;
+import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
 
 import static org.thingsboard.server.dao.service.Validator.validateId;
 
@@ -59,7 +71,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
     
     @Autowired
     private CustomerDao customerDao;
-    
+
     @Override
     public Dashboard findDashboardById(DashboardId dashboardId) {
         log.trace("Executing findDashboardById [{}]", dashboardId);
@@ -98,15 +110,63 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
     @Override
     public Dashboard assignDashboardToCustomer(DashboardId dashboardId, CustomerId customerId) {
         Dashboard dashboard = findDashboardById(dashboardId);
-        dashboard.setCustomerId(customerId);
-        return saveDashboard(dashboard);
+        Customer customer = customerDao.findById(customerId.getId());
+        if (customer == null) {
+            throw new DataValidationException("Can't assign dashboard to non-existent customer!");
+        }
+        if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
+            throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
+        }
+        if (dashboard.addAssignedCustomer(customer)) {
+            try {
+                createRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
+            } catch (ExecutionException | InterruptedException e) {
+                log.warn("[{}] Failed to create dashboard relation. Customer Id: [{}]", dashboardId, customerId);
+                throw new RuntimeException(e);
+            }
+            return saveDashboard(dashboard);
+        } else {
+            return dashboard;
+        }
     }
 
     @Override
-    public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId) {
+    public Dashboard unassignDashboardFromCustomer(DashboardId dashboardId, CustomerId customerId) {
         Dashboard dashboard = findDashboardById(dashboardId);
-        dashboard.setCustomerId(null);
-        return saveDashboard(dashboard);
+        Customer customer = customerDao.findById(customerId.getId());
+        if (customer == null) {
+            throw new DataValidationException("Can't unassign dashboard from non-existent customer!");
+        }
+        if (dashboard.removeAssignedCustomer(customer)) {
+            try {
+                deleteRelation(new EntityRelation(customerId, dashboardId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD));
+            } catch (ExecutionException | InterruptedException e) {
+                log.warn("[{}] Failed to delete dashboard relation. Customer Id: [{}]", dashboardId, customerId);
+                throw new RuntimeException(e);
+            }
+            return saveDashboard(dashboard);
+        } else {
+            return dashboard;
+        }
+    }
+
+    private Dashboard updateAssignedCustomer(DashboardId dashboardId, Customer customer) {
+        Dashboard dashboard = findDashboardById(dashboardId);
+        if (dashboard.updateAssignedCustomer(customer)) {
+            return saveDashboard(dashboard);
+        } else {
+            return dashboard;
+        }
+    }
+
+    private void deleteRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
+        log.debug("Deleting Dashboard relation: {}", dashboardRelation);
+        relationService.deleteRelationAsync(dashboardRelation).get();
+    }
+
+    private void createRelation(EntityRelation dashboardRelation) throws ExecutionException, InterruptedException {
+        log.debug("Creating Dashboard relation: {}", dashboardRelation);
+        relationService.saveRelationAsync(dashboardRelation).get();
     }
 
     @Override
@@ -134,23 +194,44 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
     }
 
     @Override
-    public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
+    public ListenableFuture<TimePageData<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) {
         log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
         Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
         Validator.validateId(customerId, "Incorrect customerId " + customerId);
         Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
-        List<DashboardInfo> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
-        return new TextPageData<>(dashboards, pageLink);
+        ListenableFuture<List<DashboardInfo>> dashboards = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
+
+        return Futures.transform(dashboards, new Function<List<DashboardInfo>, TimePageData<DashboardInfo>>() {
+            @Nullable
+            @Override
+            public TimePageData<DashboardInfo> apply(@Nullable List<DashboardInfo> dashboards) {
+                return new TimePageData<>(dashboards, pageLink);
+            }
+        });
     }
 
     @Override
-    public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId) {
-        log.trace("Executing unassignCustomerDashboards, tenantId [{}], customerId [{}]", tenantId, customerId);
-        Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+    public void unassignCustomerDashboards(CustomerId customerId) {
+        log.trace("Executing unassignCustomerDashboards, customerId [{}]", customerId);
         Validator.validateId(customerId, "Incorrect customerId " + customerId);
-        new CustomerDashboardsUnassigner(tenantId).removeEntities(customerId);
+        Customer customer = customerDao.findById(customerId.getId());
+        if (customer == null) {
+            throw new DataValidationException("Can't unassign dashboards from non-existent customer!");
+        }
+        new CustomerDashboardsUnassigner(customer).removeEntities(customer);
     }
-    
+
+    @Override
+    public void updateCustomerDashboards(CustomerId customerId) {
+        log.trace("Executing updateCustomerDashboards, customerId [{}]", customerId);
+        Validator.validateId(customerId, "Incorrect customerId " + customerId);
+        Customer customer = customerDao.findById(customerId.getId());
+        if (customer == null) {
+            throw new DataValidationException("Can't update dashboards for non-existent customer!");
+        }
+        new CustomerDashboardsUpdater(customer).removeEntities(customer);
+    }
+
     private DataValidator<Dashboard> dashboardValidator =
             new DataValidator<Dashboard>() {
                 @Override
@@ -166,17 +247,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
                             throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
                         }
                     }
-                    if (dashboard.getCustomerId() == null) {
-                        dashboard.setCustomerId(new CustomerId(ModelConstants.NULL_UUID));
-                    } else if (!dashboard.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
-                        Customer customer = customerDao.findById(dashboard.getCustomerId().getId());
-                        if (customer == null) {
-                            throw new DataValidationException("Can't assign dashboard to non-existent customer!");
-                        }
-                        if (!customer.getTenantId().getId().equals(dashboard.getTenantId().getId())) {
-                            throw new DataValidationException("Can't assign dashboard to customer from different tenant!");
-                        }
-                    }
                 }
     };
     
@@ -194,24 +264,54 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
         }
     };
     
-    private class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfo> {
-        
-        private TenantId tenantId;
+    private class CustomerDashboardsUnassigner extends TimePaginatedRemover<Customer, DashboardInfo> {
         
-        CustomerDashboardsUnassigner(TenantId tenantId) {
-            this.tenantId = tenantId;
+        private Customer customer;
+
+        CustomerDashboardsUnassigner(Customer customer) {
+            this.customer = customer;
         }
 
         @Override
-        protected List<DashboardInfo> findEntities(CustomerId id, TextPageLink pageLink) {
-            return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
+        protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
+            try {
+                return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
+            } catch (InterruptedException | ExecutionException e) {
+                log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
+                throw new RuntimeException(e);
+            }
         }
 
         @Override
         protected void removeEntity(DashboardInfo entity) {
-            unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()));
+            unassignDashboardFromCustomer(new DashboardId(entity.getUuidId()), this.customer.getId());
         }
         
     }
 
+    private class CustomerDashboardsUpdater extends TimePaginatedRemover<Customer, DashboardInfo> {
+
+        private Customer customer;
+
+        CustomerDashboardsUpdater(Customer customer) {
+            this.customer = customer;
+        }
+
+        @Override
+        protected List<DashboardInfo> findEntities(Customer customer, TimePageLink pageLink) {
+            try {
+                return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get();
+            } catch (InterruptedException | ExecutionException e) {
+                log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId());
+                throw new RuntimeException(e);
+            }
+        }
+
+        @Override
+        protected void removeEntity(DashboardInfo entity) {
+            updateAssignedCustomer(new DashboardId(entity.getUuidId()), this.customer);
+        }
+
+    }
+
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index 66e03b0..c275ad0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -266,13 +266,11 @@ public class ModelConstants {
      */
     public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard";
     public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
-    public static final String DASHBOARD_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
     public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY;
     public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration";
+    public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers";
 
     public static final String DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_tenant_and_search_text";
-    public static final String DASHBOARD_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "dashboard_by_customer_and_search_text";
-
 
     /**
      * Cassandra plugin metadata constants.
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java
index 047a8f2..0327232 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardEntity.java
@@ -19,16 +19,23 @@ import com.datastax.driver.core.utils.UUIDs;
 import com.datastax.driver.mapping.annotations.Column;
 import com.datastax.driver.mapping.annotations.PartitionKey;
 import com.datastax.driver.mapping.annotations.Table;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.Dashboard;
-import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.ShortCustomerInfo;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.dao.model.SearchTextEntity;
 import org.thingsboard.server.dao.model.type.JsonCodec;
 
+import java.io.IOException;
+import java.util.HashSet;
 import java.util.UUID;
 
 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -36,8 +43,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
 @EqualsAndHashCode
 @ToString
+@Slf4j
 public final class DashboardEntity implements SearchTextEntity<Dashboard> {
-    
+
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private static final JavaType assignedCustomersType =
+            objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
+
     @PartitionKey(value = 0)
     @Column(name = ID_PROPERTY)
     private UUID id;
@@ -46,16 +58,15 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
     @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
     private UUID tenantId;
 
-    @PartitionKey(value = 2)
-    @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
-    private UUID customerId;
-
     @Column(name = DASHBOARD_TITLE_PROPERTY)
     private String title;
     
     @Column(name = SEARCH_TEXT_PROPERTY)
     private String searchText;
-    
+
+    @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
+    private String assignedCustomers;
+
     @Column(name = DASHBOARD_CONFIGURATION_PROPERTY, codec = JsonCodec.class)
     private JsonNode configuration;
 
@@ -70,10 +81,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
         if (dashboard.getTenantId() != null) {
             this.tenantId = dashboard.getTenantId().getId();
         }
-        if (dashboard.getCustomerId() != null) {
-            this.customerId = dashboard.getCustomerId().getId();
-        }
         this.title = dashboard.getTitle();
+        if (dashboard.getAssignedCustomers() != null) {
+            try {
+                this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
+            } catch (JsonProcessingException e) {
+                log.error("Unable to serialize assigned customers to string!", e);
+            }
+        }
         this.configuration = dashboard.getConfiguration();
     }
     
@@ -93,14 +108,6 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
         this.tenantId = tenantId;
     }
 
-    public UUID getCustomerId() {
-        return customerId;
-    }
-
-    public void setCustomerId(UUID customerId) {
-        this.customerId = customerId;
-    }
-    
     public String getTitle() {
         return title;
     }
@@ -109,6 +116,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
         this.title = title;
     }
 
+    public String getAssignedCustomers() {
+        return assignedCustomers;
+    }
+
+    public void setAssignedCustomers(String assignedCustomers) {
+        this.assignedCustomers = assignedCustomers;
+    }
+
     public JsonNode getConfiguration() {
         return configuration;
     }
@@ -138,10 +153,14 @@ public final class DashboardEntity implements SearchTextEntity<Dashboard> {
         if (tenantId != null) {
             dashboard.setTenantId(new TenantId(tenantId));
         }
-        if (customerId != null) {
-            dashboard.setCustomerId(new CustomerId(customerId));
-        }
         dashboard.setTitle(title);
+        if (!StringUtils.isEmpty(assignedCustomers)) {
+            try {
+                dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
+            } catch (IOException e) {
+                log.warn("Unable to parse assigned customers!", e);
+            }
+        }
         dashboard.setConfiguration(configuration);
         return dashboard;
     }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java
index a2a5280..f64bc44 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/DashboardInfoEntity.java
@@ -19,14 +19,21 @@ import com.datastax.driver.core.utils.UUIDs;
 import com.datastax.driver.mapping.annotations.Column;
 import com.datastax.driver.mapping.annotations.PartitionKey;
 import com.datastax.driver.mapping.annotations.Table;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.EqualsAndHashCode;
 import lombok.ToString;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.DashboardInfo;
-import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.ShortCustomerInfo;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.dao.model.SearchTextEntity;
 
+import java.io.IOException;
+import java.util.HashSet;
 import java.util.UUID;
 
 import static org.thingsboard.server.dao.model.ModelConstants.*;
@@ -34,8 +41,13 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
 @Table(name = DASHBOARD_COLUMN_FAMILY_NAME)
 @EqualsAndHashCode
 @ToString
+@Slf4j
 public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
 
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private static final JavaType assignedCustomersType =
+            objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
+
     @PartitionKey(value = 0)
     @Column(name = ID_PROPERTY)
     private UUID id;
@@ -44,16 +56,15 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
     @Column(name = DASHBOARD_TENANT_ID_PROPERTY)
     private UUID tenantId;
 
-    @PartitionKey(value = 2)
-    @Column(name = DASHBOARD_CUSTOMER_ID_PROPERTY)
-    private UUID customerId;
-
     @Column(name = DASHBOARD_TITLE_PROPERTY)
     private String title;
 
     @Column(name = SEARCH_TEXT_PROPERTY)
     private String searchText;
 
+    @Column(name = DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
+    private String assignedCustomers;
+
     public DashboardInfoEntity() {
         super();
     }
@@ -65,10 +76,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
         if (dashboardInfo.getTenantId() != null) {
             this.tenantId = dashboardInfo.getTenantId().getId();
         }
-        if (dashboardInfo.getCustomerId() != null) {
-            this.customerId = dashboardInfo.getCustomerId().getId();
-        }
         this.title = dashboardInfo.getTitle();
+        if (dashboardInfo.getAssignedCustomers() != null) {
+            try {
+                this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
+            } catch (JsonProcessingException e) {
+                log.error("Unable to serialize assigned customers to string!", e);
+            }
+        }
     }
 
     public UUID getId() {
@@ -87,14 +102,6 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
         this.tenantId = tenantId;
     }
 
-    public UUID getCustomerId() {
-        return customerId;
-    }
-
-    public void setCustomerId(UUID customerId) {
-        this.customerId = customerId;
-    }
-
     public String getTitle() {
         return title;
     }
@@ -103,6 +110,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
         this.title = title;
     }
 
+    public String getAssignedCustomers() {
+        return assignedCustomers;
+    }
+
+    public void setAssignedCustomers(String assignedCustomers) {
+        this.assignedCustomers = assignedCustomers;
+    }
+
     @Override
     public String getSearchTextSource() {
         return getTitle();
@@ -124,10 +139,14 @@ public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
         if (tenantId != null) {
             dashboardInfo.setTenantId(new TenantId(tenantId));
         }
-        if (customerId != null) {
-            dashboardInfo.setCustomerId(new CustomerId(customerId));
-        }
         dashboardInfo.setTitle(title);
+        if (!StringUtils.isEmpty(assignedCustomers)) {
+            try {
+                dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
+            } catch (IOException e) {
+                log.warn("Unable to parse assigned customers!", e);
+            }
+        }
         return dashboardInfo;
     }
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
index bd0e0dc..99f65f0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java
@@ -16,13 +16,18 @@
 package org.thingsboard.server.dao.model.sql;
 
 import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
 import org.hibernate.annotations.Type;
 import org.hibernate.annotations.TypeDef;
+import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.Dashboard;
-import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.ShortCustomerInfo;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -33,26 +38,33 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Table;
+import java.io.IOException;
+import java.util.HashSet;
 
 @Data
+@Slf4j
 @EqualsAndHashCode(callSuper = true)
 @Entity
 @TypeDef(name = "json", typeClass = JsonStringType.class)
 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
 public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements SearchTextEntity<Dashboard> {
 
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private static final JavaType assignedCustomersType =
+            objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
+
     @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
     private String tenantId;
 
-    @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
-    private String customerId;
-
     @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
     private String title;
     
     @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
     private String searchText;
 
+    @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
+    private String assignedCustomers;
+
     @Type(type = "json")
     @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY)
     private JsonNode configuration;
@@ -68,10 +80,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
         if (dashboard.getTenantId() != null) {
             this.tenantId = toString(dashboard.getTenantId().getId());
         }
-        if (dashboard.getCustomerId() != null) {
-            this.customerId = toString(dashboard.getCustomerId().getId());
-        }
         this.title = dashboard.getTitle();
+        if (dashboard.getAssignedCustomers() != null) {
+            try {
+                this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers());
+            } catch (JsonProcessingException e) {
+                log.error("Unable to serialize assigned customers to string!", e);
+            }
+        }
         this.configuration = dashboard.getConfiguration();
     }
 
@@ -92,10 +108,14 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S
         if (tenantId != null) {
             dashboard.setTenantId(new TenantId(toUUID(tenantId)));
         }
-        if (customerId != null) {
-            dashboard.setCustomerId(new CustomerId(toUUID(customerId)));
-        }
         dashboard.setTitle(title);
+        if (!StringUtils.isEmpty(assignedCustomers)) {
+            try {
+                dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
+            } catch (IOException e) {
+                log.warn("Unable to parse assigned customers!", e);
+            }
+        }
         dashboard.setConfiguration(configuration);
         return dashboard;
     }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java
index c87e97d..7c295d1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java
@@ -16,10 +16,15 @@
 package org.thingsboard.server.dao.model.sql;
 
 import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.util.StringUtils;
 import org.thingsboard.server.common.data.DashboardInfo;
-import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.ShortCustomerInfo;
 import org.thingsboard.server.common.data.id.DashboardId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.dao.model.BaseSqlEntity;
@@ -29,25 +34,32 @@ import org.thingsboard.server.dao.model.SearchTextEntity;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Table;
+import java.io.IOException;
+import java.util.HashSet;
 
 @Data
+@Slf4j
 @EqualsAndHashCode(callSuper = true)
 @Entity
 @Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
 public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements SearchTextEntity<DashboardInfo> {
 
+    private static final ObjectMapper objectMapper = new ObjectMapper();
+    private static final JavaType assignedCustomersType =
+            objectMapper.getTypeFactory().constructCollectionType(HashSet.class, ShortCustomerInfo.class);
+
     @Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
     private String tenantId;
 
-    @Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
-    private String customerId;
-
     @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
     private String title;
 
     @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
     private String searchText;
 
+    @Column(name = ModelConstants.DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY)
+    private String assignedCustomers;
+
     public DashboardInfoEntity() {
         super();
     }
@@ -59,10 +71,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
         if (dashboardInfo.getTenantId() != null) {
             this.tenantId = toString(dashboardInfo.getTenantId().getId());
         }
-        if (dashboardInfo.getCustomerId() != null) {
-            this.customerId = toString(dashboardInfo.getCustomerId().getId());
-        }
         this.title = dashboardInfo.getTitle();
+        if (dashboardInfo.getAssignedCustomers() != null) {
+            try {
+                this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers());
+            } catch (JsonProcessingException e) {
+                log.error("Unable to serialize assigned customers to string!", e);
+            }
+        }
     }
 
     @Override
@@ -86,10 +102,14 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements
         if (tenantId != null) {
             dashboardInfo.setTenantId(new TenantId(toUUID(tenantId)));
         }
-        if (customerId != null) {
-            dashboardInfo.setCustomerId(new CustomerId(toUUID(customerId)));
-        }
         dashboardInfo.setTitle(title);
+        if (!StringUtils.isEmpty(assignedCustomers)) {
+            try {
+                dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType));
+            } catch (IOException e) {
+                log.warn("Unable to parse assigned customers!", e);
+            }
+        }
         return dashboardInfo;
     }
 
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java b/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java
new file mode 100644
index 0000000..b52f747
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/TimePaginatedRemover.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service;
+
+import org.thingsboard.server.common.data.id.IdBased;
+import org.thingsboard.server.common.data.page.TimePageLink;
+
+import java.sql.Time;
+import java.util.List;
+import java.util.UUID;
+
+public abstract class TimePaginatedRemover<I, D extends IdBased<?>> {
+
+    private static final int DEFAULT_LIMIT = 100;
+
+    public void removeEntities(I id) {
+        TimePageLink pageLink = new TimePageLink(DEFAULT_LIMIT);
+        boolean hasNext = true;
+        while (hasNext) {
+            List<D> entities = findEntities(id, pageLink);
+            for (D entity : entities) {
+                removeEntity(entity);
+            }
+            hasNext = entities.size() == pageLink.getLimit();
+            if (hasNext) {
+                int index = entities.size() - 1;
+                UUID idOffset = entities.get(index).getUuidId();
+                pageLink.setIdOffset(idOffset);
+            }
+        }
+    }
+
+    protected abstract List<D> findEntities(I id, TimePageLink pageLink);
+
+    protected abstract void removeEntity(D entity);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
index d8a511d..d962977 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service;
 
 import org.thingsboard.server.common.data.id.EntityId;
 import org.thingsboard.server.common.data.id.UUIDBased;
+import org.thingsboard.server.common.data.page.BasePageLink;
 import org.thingsboard.server.common.data.page.TextPageLink;
 import org.thingsboard.server.dao.exception.IncorrectParameterException;
 
@@ -116,7 +117,7 @@ public class Validator {
      * @param pageLink     the page link
      * @param errorMessage the error message for exception
      */
-    public static void validatePageLink(TextPageLink pageLink, String errorMessage) {
+    public static void validatePageLink(BasePageLink pageLink, String errorMessage) {
         if (pageLink == null || pageLink.getLimit() < 1 || (pageLink.getIdOffset() != null && pageLink.getIdOffset().version() != 1)) {
             throw new IncorrectParameterException(errorMessage);
         }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
index 7f0ec7a..ef7d0a0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
@@ -39,12 +39,4 @@ public interface DashboardInfoRepository extends CrudRepository<DashboardInfoEnt
                                              @Param("idOffset") String idOffset,
                                              Pageable pageable);
 
-    @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +
-            "AND di.customerId = :customerId AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%')) " +
-            "AND di.id > :idOffset ORDER BY di.id")
-    List<DashboardInfoEntity> findByTenantIdAndCustomerId(@Param("tenantId") String tenantId,
-                                                          @Param("customerId") String customerId,
-                                                          @Param("searchText") String searchText,
-                                                          @Param("idOffset") String idOffset,
-                                                          Pageable pageable);
 }
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
index 204189a..1e343a4 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
@@ -15,19 +15,31 @@
  */
 package org.thingsboard.server.dao.sql.dashboard;
 
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.domain.PageRequest;
 import org.springframework.data.repository.CrudRepository;
 import org.springframework.stereotype.Component;
 import org.thingsboard.server.common.data.DashboardInfo;
+import org.thingsboard.server.common.data.EntityType;
 import org.thingsboard.server.common.data.UUIDConverter;
+import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
+import org.thingsboard.server.common.data.relation.RelationTypeGroup;
 import org.thingsboard.server.dao.DaoUtil;
 import org.thingsboard.server.dao.dashboard.DashboardInfoDao;
 import org.thingsboard.server.dao.model.sql.DashboardInfoEntity;
+import org.thingsboard.server.dao.relation.RelationDao;
 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
 import org.thingsboard.server.dao.util.SqlDao;
 
+import java.sql.Time;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
@@ -37,11 +49,15 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
 /**
  * Created by Valerii Sosliuk on 5/6/2017.
  */
+@Slf4j
 @Component
 @SqlDao
 public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoEntity, DashboardInfo> implements DashboardInfoDao {
 
     @Autowired
+    private RelationDao relationDao;
+
+    @Autowired
     private DashboardInfoRepository dashboardInfoRepository;
 
     @Override
@@ -65,13 +81,17 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
     }
 
     @Override
-    public List<DashboardInfo> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
-        return DaoUtil.convertDataList(dashboardInfoRepository
-                .findByTenantIdAndCustomerId(
-                        UUIDConverter.fromTimeUUID(tenantId),
-                        UUIDConverter.fromTimeUUID(customerId),
-                        Objects.toString(pageLink.getTextSearch(), ""),
-                        pageLink.getIdOffset() == null ? NULL_UUID_STR : UUIDConverter.fromTimeUUID(pageLink.getIdOffset()),
-                        new PageRequest(0, pageLink.getLimit())));
+    public ListenableFuture<List<DashboardInfo>> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) {
+        log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
+
+        ListenableFuture<List<EntityRelation>> relations = relationDao.findRelations(new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink);
+
+        return Futures.transform(relations, (AsyncFunction<List<EntityRelation>, List<DashboardInfo>>) input -> {
+            List<ListenableFuture<DashboardInfo>> dashboardFutures = new ArrayList<>(input.size());
+            for (EntityRelation relation : input) {
+                dashboardFutures.add(findByIdAsync(relation.getTo().getId()));
+            }
+            return Futures.successfulAsList(dashboardFutures);
+        });
     }
 }
diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql
index bc7884d..dc83045 100644
--- a/dao/src/main/resources/cassandra/schema.cql
+++ b/dao/src/main/resources/cassandra/schema.cql
@@ -364,26 +364,19 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.widget_type_by_tenant_and_ali
 CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
 	id timeuuid,
 	tenant_id timeuuid,
-	customer_id timeuuid,
 	title text,
 	search_text text,
+	assigned_customers text,
 	configuration text,
-	PRIMARY KEY (id, tenant_id, customer_id)
+	PRIMARY KEY (id, tenant_id)
 );
 
 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
 	SELECT *
 	from thingsboard.dashboard
-	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
-	PRIMARY KEY ( tenant_id, search_text, id, customer_id )
-	WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC );
-
-CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
-	SELECT *
-	from thingsboard.dashboard
-	WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
-	PRIMARY KEY ( customer_id, tenant_id, search_text, id )
-	WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+	WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+	PRIMARY KEY ( tenant_id, search_text, id )
+	WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
 
 CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
     entity_type text, // (DEVICE, CUSTOMER, TENANT)
diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql
index 1f739f8..53ec223 100644
--- a/dao/src/main/resources/sql/schema.sql
+++ b/dao/src/main/resources/sql/schema.sql
@@ -105,7 +105,7 @@ CREATE TABLE IF NOT EXISTS customer (
 CREATE TABLE IF NOT EXISTS dashboard (
     id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
     configuration varchar(10000000),
-    customer_id varchar(31),
+    assigned_customers varchar(1000000),
     search_text varchar(255),
     tenant_id varchar(31),
     title varchar(255)
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
index f0ed0b3..380c65e 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
@@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.CustomerId;
 import org.thingsboard.server.common.data.id.TenantId;
 import org.thingsboard.server.common.data.page.TextPageData;
 import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.page.TimePageData;
+import org.thingsboard.server.common.data.page.TimePageLink;
 import org.thingsboard.server.dao.exception.DataValidationException;
 import org.thingsboard.server.dao.model.ModelConstants;
 
 import java.io.IOException;
+import java.sql.Time;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 
 public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
     
@@ -68,8 +72,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
         Assert.assertNotNull(savedDashboard.getId());
         Assert.assertTrue(savedDashboard.getCreatedTime() > 0);
         Assert.assertEquals(dashboard.getTenantId(), savedDashboard.getTenantId());
-        Assert.assertNotNull(savedDashboard.getCustomerId());
-        Assert.assertEquals(ModelConstants.NULL_UUID, savedDashboard.getCustomerId().getId());
         Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle());
         
         savedDashboard.setTitle("My new dashboard");
@@ -280,7 +282,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
     }
     
     @Test
-    public void testFindDashboardsByTenantIdAndCustomerId() {
+    public void testFindDashboardsByTenantIdAndCustomerId() throws ExecutionException, InterruptedException {
         Tenant tenant = new Tenant();
         tenant.setTitle("Test tenant");
         tenant = tenantService.saveTenant(tenant);
@@ -303,10 +305,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
         }
         
         List<DashboardInfo> loadedDashboards = new ArrayList<>();
-        TextPageLink pageLink = new TextPageLink(23);
-        TextPageData<DashboardInfo> pageData = null;
+        TimePageLink pageLink = new TimePageLink(23);
+        TimePageData<DashboardInfo> pageData = null;
         do {
-            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
             loadedDashboards.addAll(pageData.getData());
             if (pageData.hasNext()) {
                 pageLink = pageData.getNextPageLink();
@@ -318,98 +320,14 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
         
         Assert.assertEquals(dashboards, loadedDashboards);
         
-        dashboardService.unassignCustomerDashboards(tenantId, customerId);
+        dashboardService.unassignCustomerDashboards(customerId);
 
-        pageLink = new TextPageLink(42);
-        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+        pageLink = new TimePageLink(42);
+        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get();
         Assert.assertFalse(pageData.hasNext());
         Assert.assertTrue(pageData.getData().isEmpty());
         
         tenantService.deleteTenant(tenantId);
     }
-    
-    @Test
-    public void testFindDashboardsByTenantIdCustomerIdAndTitle() {
-        
-        Customer customer = new Customer();
-        customer.setTitle("Test customer");
-        customer.setTenantId(tenantId);
-        customer = customerService.saveCustomer(customer);
-        CustomerId customerId = customer.getId();
-        
-        String title1 = "Dashboard title 1";
-        List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
-        for (int i=0;i<124;i++) {
-            Dashboard dashboard = new Dashboard();
-            dashboard.setTenantId(tenantId);
-            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
-            String title = title1+suffix;
-            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
-            dashboard.setTitle(title);
-            dashboard = dashboardService.saveDashboard(dashboard);
-            dashboardsTitle1.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
-        }
-        String title2 = "Dashboard title 2";
-        List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
-        for (int i=0;i<151;i++) {
-            Dashboard dashboard = new Dashboard();
-            dashboard.setTenantId(tenantId);
-            String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
-            String title = title2+suffix;
-            title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
-            dashboard.setTitle(title);
-            dashboard = dashboardService.saveDashboard(dashboard);
-            dashboardsTitle2.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
-        }
-        
-        List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
-        TextPageLink pageLink = new TextPageLink(24, title1);
-        TextPageData<DashboardInfo> pageData = null;
-        do {
-            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
-            loadedDashboardsTitle1.addAll(pageData.getData());
-            if (pageData.hasNext()) {
-                pageLink = pageData.getNextPageLink();
-            }
-        } while (pageData.hasNext());
-        
-        Collections.sort(dashboardsTitle1, idComparator);
-        Collections.sort(loadedDashboardsTitle1, idComparator);
-        
-        Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
-        
-        List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
-        pageLink = new TextPageLink(4, title2);
-        do {
-            pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
-            loadedDashboardsTitle2.addAll(pageData.getData());
-            if (pageData.hasNext()) {
-                pageLink = pageData.getNextPageLink();
-            }
-        } while (pageData.hasNext());
 
-        Collections.sort(dashboardsTitle2, idComparator);
-        Collections.sort(loadedDashboardsTitle2, idComparator);
-        
-        Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
-
-        for (DashboardInfo dashboard : loadedDashboardsTitle1) {
-            dashboardService.deleteDashboard(dashboard.getId());
-        }
-        
-        pageLink = new TextPageLink(4, title1);
-        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
-        Assert.assertFalse(pageData.hasNext());
-        Assert.assertEquals(0, pageData.getData().size());
-        
-        for (DashboardInfo dashboard : loadedDashboardsTitle2) {
-            dashboardService.deleteDashboard(dashboard.getId());
-        }
-        
-        pageLink = new TextPageLink(4, title2);
-        pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
-        Assert.assertFalse(pageData.hasNext());
-        Assert.assertEquals(0, pageData.getData().size());
-        customerService.deleteCustomer(customerId);
-    }
 }
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java
index 39bb084..3648cce 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDaoTest.java
@@ -16,6 +16,7 @@
 package org.thingsboard.server.dao.sql.dashboard;
 
 import com.datastax.driver.core.utils.UUIDs;
+import org.junit.Assert;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.thingsboard.server.common.data.DashboardInfo;
@@ -40,53 +41,26 @@ public class JpaDashboardInfoDaoTest extends AbstractJpaDaoTest {
     @Test
     public void testFindDashboardsByTenantId() {
         UUID tenantId1 = UUIDs.timeBased();
-        UUID customerId1 = UUIDs.timeBased();
         UUID tenantId2 = UUIDs.timeBased();
-        UUID customerId2 = UUIDs.timeBased();
 
         for (int i = 0; i < 20; i++) {
-            createDashboard(tenantId1, customerId1, i);
-            createDashboard(tenantId2, customerId2, i * 2);
+            createDashboard(tenantId1, i);
+            createDashboard(tenantId2, i * 2);
         }
 
         TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
         List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink1);
-        assertEquals(15, dashboardInfos1.size());
+        Assert.assertEquals(15, dashboardInfos1.size());
 
         TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
         List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantId(tenantId1, pageLink2);
-        assertEquals(5, dashboardInfos2.size());
+        Assert.assertEquals(5, dashboardInfos2.size());
     }
 
-    @Test
-    public void testFindDashboardsByTenantAndCustomerId() {
-        UUID tenantId1 = UUIDs.timeBased();
-        UUID customerId1 = UUIDs.timeBased();
-        UUID tenantId2 = UUIDs.timeBased();
-        UUID customerId2 = UUIDs.timeBased();
-
-        for (int i = 0; i < 20; i++) {
-            createDashboard(tenantId1, customerId1, i);
-            createDashboard(tenantId2, customerId2, i * 2);
-        }
-
-        TextPageLink pageLink1 = new TextPageLink(15, "DASHBOARD");
-        List<DashboardInfo> dashboardInfos1 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink1);
-        assertEquals(15, dashboardInfos1.size());
-
-        TextPageLink pageLink2 = new TextPageLink(15, "DASHBOARD", dashboardInfos1.get(14).getId().getId(), null);
-        List<DashboardInfo> dashboardInfos2 = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink2);
-        assertEquals(5, dashboardInfos2.size());
-    }
-
-    private void assertEquals(int i, int size) {
-    }
-
-    private void createDashboard(UUID tenantId, UUID customerId, int index) {
+    private void createDashboard(UUID tenantId, int index) {
         DashboardInfo dashboardInfo = new DashboardInfo();
         dashboardInfo.setId(new DashboardId(UUIDs.timeBased()));
         dashboardInfo.setTenantId(new TenantId(tenantId));
-        dashboardInfo.setCustomerId(new CustomerId(customerId));
         dashboardInfo.setTitle("DASHBOARD_" + index);
         dashboardInfoDao.save(dashboardInfo);
     }
diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js
index 3082bd3..ae11f84 100644
--- a/ui/src/app/api/dashboard.service.js
+++ b/ui/src/app/api/dashboard.service.js
@@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', [])
     .factory('dashboardService', DashboardService).name;
 
 /*@ngInject*/
-function DashboardService($rootScope, $http, $q, $location, customerService) {
+function DashboardService($rootScope, $http, $q, $location, $filter) {
 
     var stDiffPromise;
 
@@ -37,7 +37,11 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
         deleteDashboard: deleteDashboard,
         saveDashboard: saveDashboard,
         unassignDashboardFromCustomer: unassignDashboardFromCustomer,
+        updateDashboardCustomers: updateDashboardCustomers,
+        addDashboardCustomers: addDashboardCustomers,
+        removeDashboardCustomers: removeDashboardCustomers,
         makeDashboardPublic: makeDashboardPublic,
+        makeDashboardPrivate: makeDashboardPrivate,
         getPublicDashboardLink: getPublicDashboardLink
     }
 
@@ -56,14 +60,14 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
             url += '&textOffset=' + pageLink.textOffset;
         }
         $http.get(url, config).then(function success(response) {
-            deferred.resolve(response.data);
+            deferred.resolve(prepareDashboards(response.data));
         }, function fail() {
             deferred.reject();
         });
         return deferred.promise;
     }
 
-    function getTenantDashboards(pageLink, applyCustomersInfo, config) {
+    function getTenantDashboards(pageLink, config) {
         var deferred = $q.defer();
         var url = '/api/tenant/dashboards?limit=' + pageLink.limit;
         if (angular.isDefined(pageLink.textSearch)) {
@@ -76,51 +80,25 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
             url += '&textOffset=' + pageLink.textOffset;
         }
         $http.get(url, config).then(function success(response) {
-            if (applyCustomersInfo) {
-                customerService.applyAssignedCustomersInfo(response.data.data).then(
-                    function success(data) {
-                        response.data.data = data;
-                        deferred.resolve(response.data);
-                    },
-                    function fail() {
-                        deferred.reject();
-                    }
-                );
-            } else {
-                deferred.resolve(response.data);
-            }
+            deferred.resolve(prepareDashboards(response.data));
         }, function fail() {
             deferred.reject();
         });
         return deferred.promise;
     }
 
-    function getCustomerDashboards(customerId, pageLink, applyCustomersInfo, config) {
+    function getCustomerDashboards(customerId, pageLink, config) {
         var deferred = $q.defer();
         var url = '/api/customer/' + customerId + '/dashboards?limit=' + pageLink.limit;
-        if (angular.isDefined(pageLink.textSearch)) {
-            url += '&textSearch=' + pageLink.textSearch;
-        }
         if (angular.isDefined(pageLink.idOffset)) {
-            url += '&idOffset=' + pageLink.idOffset;
-        }
-        if (angular.isDefined(pageLink.textOffset)) {
-            url += '&textOffset=' + pageLink.textOffset;
+            url += '&offset=' + pageLink.idOffset;
         }
         $http.get(url, config).then(function success(response) {
-            if (applyCustomersInfo) {
-                customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
-                    function success(data) {
-                        response.data.data = data;
-                        deferred.resolve(response.data);
-                    },
-                    function fail() {
-                        deferred.reject();
-                    }
-                );
-            } else {
-                deferred.resolve(response.data);
+            response.data = prepareDashboards(response.data);
+            if (pageLink.textSearch) {
+                response.data.data = $filter('filter')(response.data.data, {title: pageLink.textSearch});
             }
+            deferred.resolve(response.data);
         }, function fail() {
             deferred.reject();
         });
@@ -151,7 +129,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
         var deferred = $q.defer();
         var url = '/api/dashboard/' + dashboardId;
         $http.get(url, null).then(function success(response) {
-            deferred.resolve(response.data);
+            deferred.resolve(prepareDashboard(response.data));
         }, function fail() {
             deferred.reject();
         });
@@ -162,7 +140,7 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
         var deferred = $q.defer();
         var url = '/api/dashboard/info/' + dashboardId;
         $http.get(url, config).then(function success(response) {
-            deferred.resolve(response.data);
+            deferred.resolve(prepareDashboard(response.data));
         }, function fail() {
             deferred.reject();
         });
@@ -172,8 +150,8 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
     function saveDashboard(dashboard) {
         var deferred = $q.defer();
         var url = '/api/dashboard';
-        $http.post(url, dashboard).then(function success(response) {
-            deferred.resolve(response.data);
+        $http.post(url, cleanDashboard(dashboard)).then(function success(response) {
+            deferred.resolve(prepareDashboard(response.data));
         }, function fail() {
             deferred.reject();
         });
@@ -195,18 +173,51 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
         var deferred = $q.defer();
         var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
         $http.post(url, null).then(function success(response) {
-            deferred.resolve(response.data);
+            deferred.resolve(prepareDashboard(response.data));
         }, function fail() {
             deferred.reject();
         });
         return deferred.promise;
     }
 
-    function unassignDashboardFromCustomer(dashboardId) {
+    function unassignDashboardFromCustomer(customerId, dashboardId) {
         var deferred = $q.defer();
-        var url = '/api/customer/dashboard/' + dashboardId;
+        var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
         $http.delete(url).then(function success(response) {
-            deferred.resolve(response.data);
+            deferred.resolve(prepareDashboard(response.data));
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function updateDashboardCustomers(dashboardId, customerIds) {
+        var deferred = $q.defer();
+        var url = '/api/dashboard/' + dashboardId + '/customers';
+        $http.post(url, customerIds).then(function success(response) {
+            deferred.resolve(prepareDashboard(response.data));
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function addDashboardCustomers(dashboardId, customerIds) {
+        var deferred = $q.defer();
+        var url = '/api/dashboard/' + dashboardId + '/customers/add';
+        $http.post(url, customerIds).then(function success(response) {
+            deferred.resolve(prepareDashboard(response.data));
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function removeDashboardCustomers(dashboardId, customerIds) {
+        var deferred = $q.defer();
+        var url = '/api/dashboard/' + dashboardId + '/customers/remove';
+        $http.post(url, customerIds).then(function success(response) {
+            deferred.resolve(prepareDashboard(response.data));
         }, function fail() {
             deferred.reject();
         });
@@ -217,7 +228,18 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
         var deferred = $q.defer();
         var url = '/api/customer/public/dashboard/' + dashboardId;
         $http.post(url, null).then(function success(response) {
-            deferred.resolve(response.data);
+            deferred.resolve(prepareDashboard(response.data));
+        }, function fail() {
+            deferred.reject();
+        });
+        return deferred.promise;
+    }
+
+    function makeDashboardPrivate(dashboardId) {
+        var deferred = $q.defer();
+        var url = '/api/customer/public/dashboard/' + dashboardId;
+        $http.delete(url).then(function success(response) {
+            deferred.resolve(prepareDashboard(response.data));
         }, function fail() {
             deferred.reject();
         });
@@ -230,8 +252,44 @@ function DashboardService($rootScope, $http, $q, $location, customerService) {
         if (port != 80 && port != 443) {
             url += ":" + port;
         }
-        url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.customerId.id;
+        url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
         return url;
     }
 
+    function prepareDashboards(dashboardsData) {
+        if (dashboardsData.data) {
+            for (var i = 0; i < dashboardsData.data.length; i++) {
+                dashboardsData.data[i] = prepareDashboard(dashboardsData.data[i]);
+            }
+        }
+        return dashboardsData;
+    }
+
+    function prepareDashboard(dashboard) {
+        dashboard.publicCustomerId = null;
+        dashboard.assignedCustomersText = "";
+        dashboard.assignedCustomersIds = [];
+        if (dashboard.assignedCustomers && dashboard.assignedCustomers.length) {
+            var assignedCustomersTitles = [];
+            for (var i = 0; i < dashboard.assignedCustomers.length; i++) {
+                var assignedCustomer = dashboard.assignedCustomers[i];
+                dashboard.assignedCustomersIds.push(assignedCustomer.customerId.id);
+                if (assignedCustomer.public) {
+                    dashboard.publicCustomerId = assignedCustomer.customerId.id;
+                } else {
+                    assignedCustomersTitles.push(assignedCustomer.title);
+                }
+            }
+            dashboard.assignedCustomersText = assignedCustomersTitles.join(', ');
+        }
+        return dashboard;
+    }
+
+    function cleanDashboard(dashboard) {
+        delete dashboard.publicCustomerId;
+        delete dashboard.assignedCustomersText;
+        delete dashboard.assignedCustomersIds;
+        return dashboard;
+    }
+
 }
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index df1c3e0..d30c10c 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -273,9 +273,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 break;
             case types.entityType.dashboard:
                 if (user.authority === 'CUSTOMER_USER') {
-                    promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config);
+                    promise = dashboardService.getCustomerDashboards(customerId, pageLink, config);
                 } else {
-                    promise = dashboardService.getTenantDashboards(pageLink, false, config);
+                    promise = dashboardService.getTenantDashboards(pageLink, config);
                 }
                 break;
             case types.entityType.user:
@@ -403,6 +403,21 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return deferred.promise;
     }
 
+    function resolveAliasEntityId(entityType, id) {
+        var entityId = {
+            entityType: entityType,
+            id: id
+        };
+        if (entityType == types.aliasEntityType.current_customer) {
+            var user = userService.getCurrentUser();
+            entityId.entityType = types.entityType.customer;
+            if (user.authority === 'CUSTOMER_USER') {
+                entityId.id = user.customerId;
+            }
+        }
+        return entityId;
+    }
+
     function getStateEntityId(filter, stateParams) {
         var entityId = null;
         if (stateParams) {
@@ -417,6 +432,9 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         if (!entityId) {
             entityId = filter.defaultStateEntity;
         }
+        if (entityId) {
+            entityId = resolveAliasEntityId(entityId.entityType, entityId.id);
+        }
         return entityId;
     }
 
@@ -432,7 +450,8 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         var stateEntityId = getStateEntityId(filter, stateParams);
         switch (filter.type) {
             case types.aliasFilterType.singleEntity.value:
-                getEntity(filter.singleEntity.entityType, filter.singleEntity.id, {ignoreLoading: true}).then(
+                var aliasEntityId = resolveAliasEntityId(filter.singleEntity.entityType, filter.singleEntity.id);
+                getEntity(aliasEntityId.entityType, aliasEntityId.id, {ignoreLoading: true}).then(
                     function success(entity) {
                         result.entities = entitiesToEntitiesInfo([entity]);
                         deferred.resolve(result);
@@ -530,10 +549,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     rootEntityId = filter.rootEntity.id;
                 }
                 if (rootEntityType && rootEntityId) {
+                    var relationQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
                     var searchQuery = {
                         parameters: {
-                            rootId: rootEntityId,
-                            rootType: rootEntityType,
+                            rootId: relationQueryRootEntityId.id,
+                            rootType: relationQueryRootEntityId.entityType,
                             direction: filter.direction
                         },
                         filters: filter.filters
@@ -571,10 +591,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                     rootEntityId = filter.rootEntity.id;
                 }
                 if (rootEntityType && rootEntityId) {
+                    var searchQueryRootEntityId = resolveAliasEntityId(rootEntityType, rootEntityId);
                     searchQuery = {
                         parameters: {
-                            rootId: rootEntityId,
-                            rootType: rootEntityType,
+                            rootId: searchQueryRootEntityId.id,
+                            rootType: searchQueryRootEntityId.entityType,
                             direction: filter.direction
                         },
                         relationType: filter.relationType
@@ -709,7 +730,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
         return result;
     }
 
-    function prepareAllowedEntityTypesList(allowedEntityTypes) {
+    function prepareAllowedEntityTypesList(allowedEntityTypes, useAliasEntityTypes) {
         var authority = userService.getAuthority();
         var entityTypes = {};
         switch(authority) {
@@ -726,12 +747,18 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
                 entityTypes.rule = types.entityType.rule;
                 entityTypes.plugin = types.entityType.plugin;
                 entityTypes.dashboard = types.entityType.dashboard;
+                if (useAliasEntityTypes) {
+                    entityTypes.current_customer = types.aliasEntityType.current_customer;
+                }
                 break;
             case 'CUSTOMER_USER':
                 entityTypes.device = types.entityType.device;
                 entityTypes.asset = types.entityType.asset;
                 entityTypes.customer = types.entityType.customer;
                 entityTypes.dashboard = types.entityType.dashboard;
+                if (useAliasEntityTypes) {
+                    entityTypes.current_customer = types.aliasEntityType.current_customer;
+                }
                 break;
         }
 
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index ce5e673..c969c2f 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -266,9 +266,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
             var pageLink = {limit: 100};
             var fetchDashboardsPromise;
             if (currentUser.authority === 'TENANT_ADMIN') {
-                fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink, false);
+                fetchDashboardsPromise = dashboardService.getTenantDashboards(pageLink);
             } else {
-                fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink, false);
+                fetchDashboardsPromise = dashboardService.getCustomerDashboards(currentUser.customerId, pageLink);
             }
             fetchDashboardsPromise.then(
                 function success(result) {
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index d82add1..6f09f34 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -296,6 +296,9 @@ export default angular.module('thingsboard.types', [])
                 dashboard: "DASHBOARD",
                 alarm: "ALARM"
             },
+            aliasEntityType: {
+                current_customer: "CURRENT_CUSTOMER"
+            },
             entityTypeTranslations: {
                 "DEVICE": {
                     type: 'entity.type-device',
@@ -350,6 +353,10 @@ export default angular.module('thingsboard.types', [])
                     typePlural: 'entity.type-alarms',
                     list: 'entity.list-of-alarms',
                     nameStartsWith: 'entity.alarm-name-starts-with'
+                },
+                "CURRENT_CUSTOMER": {
+                    type: 'entity.type-current-customer',
+                    list: 'entity.type-current-customer'
                 }
             },
             entitySearchDirection: {
diff --git a/ui/src/app/components/dashboard-autocomplete.directive.js b/ui/src/app/components/dashboard-autocomplete.directive.js
index 2235b82..6af5585 100644
--- a/ui/src/app/components/dashboard-autocomplete.directive.js
+++ b/ui/src/app/components/dashboard-autocomplete.directive.js
@@ -48,7 +48,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
             var promise;
             if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
                 if (scope.customerId) {
-                    promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true});
+                    promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
                 } else {
                     promise = $q.when({data: []});
                 }
@@ -60,7 +60,7 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u
                         promise = $q.when({data: []});
                     }
                 } else {
-                    promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true});
+                    promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
                 }
             }
 
diff --git a/ui/src/app/components/dashboard-select.directive.js b/ui/src/app/components/dashboard-select.directive.js
index ac5cd3d..b172168 100644
--- a/ui/src/app/components/dashboard-select.directive.js
+++ b/ui/src/app/components/dashboard-select.directive.js
@@ -48,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu
         var promise;
         if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
             if (scope.customerId && scope.customerId != types.id.nullUid) {
-                promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true});
+                promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, {ignoreLoading: true});
             } else {
                 promise = $q.when({data: []});
             }
         } else {
-            promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true});
+            promise = dashboardService.getTenantDashboards(pageLink, {ignoreLoading: true});
         }
 
         promise.then(function success(result) {
diff --git a/ui/src/app/dashboard/add-dashboards-to-customer.controller.js b/ui/src/app/dashboard/add-dashboards-to-customer.controller.js
index 6fe6e79..b0bbef1 100644
--- a/ui/src/app/dashboard/add-dashboards-to-customer.controller.js
+++ b/ui/src/app/dashboard/add-dashboards-to-customer.controller.js
@@ -52,7 +52,7 @@ export default function AddDashboardsToCustomerController(dashboardService, $mdD
         fetchMoreItems_: function () {
             if (vm.dashboards.hasNext && !vm.dashboards.pending) {
                 vm.dashboards.pending = true;
-                dashboardService.getTenantDashboards(vm.dashboards.nextPageLink, false).then(
+                dashboardService.getTenantDashboards(vm.dashboards.nextPageLink).then(
                     function success(dashboards) {
                         vm.dashboards.data = vm.dashboards.data.concat(dashboards.data);
                         vm.dashboards.nextPageLink = dashboards.nextPageLink;
diff --git a/ui/src/app/dashboard/dashboard.directive.js b/ui/src/app/dashboard/dashboard.directive.js
index b2f3117..39b1c02 100644
--- a/ui/src/app/dashboard/dashboard.directive.js
+++ b/ui/src/app/dashboard/dashboard.directive.js
@@ -20,36 +20,17 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
 /* eslint-enable import/no-unresolved, import/default */
 
 /*@ngInject*/
-export default function DashboardDirective($compile, $templateCache, $translate, types, toast, customerService, dashboardService) {
+export default function DashboardDirective($compile, $templateCache, $translate, types, toast, dashboardService) {
     var linker = function (scope, element) {
         var template = $templateCache.get(dashboardFieldsetTemplate);
         element.html(template);
-
-        scope.isAssignedToCustomer = false;
-        scope.isPublic = false;
-        scope.assignedCustomer = null;
         scope.publicLink = null;
-
         scope.$watch('dashboard', function(newVal) {
             if (newVal) {
-                if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) {
-                    scope.isAssignedToCustomer = true;
-                    customerService.getShortCustomerInfo(scope.dashboard.customerId.id).then(
-                        function success(customer) {
-                            scope.assignedCustomer = customer;
-                            scope.isPublic = customer.isPublic;
-                            if (scope.isPublic) {
-                                scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
-                            } else {
-                                scope.publicLink = null;
-                            }
-                        }
-                    );
+                if (scope.dashboard.publicCustomerId) {
+                    scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard);
                 } else {
-                    scope.isAssignedToCustomer = false;
-                    scope.isPublic = false;
                     scope.publicLink = null;
-                    scope.assignedCustomer = null;
                 }
             }
         });
@@ -66,10 +47,12 @@ export default function DashboardDirective($compile, $templateCache, $translate,
         scope: {
             dashboard: '=',
             isEdit: '=',
+            customerId: '=',
             dashboardScope: '=',
             theForm: '=',
-            onAssignToCustomer: '&',
             onMakePublic: '&',
+            onMakePrivate: '&',
+            onManageAssignedCustomers: '&',
             onUnassignFromCustomer: '&',
             onExportDashboard: '&',
             onDeleteDashboard: '&'
diff --git a/ui/src/app/dashboard/dashboard-card.scss b/ui/src/app/dashboard/dashboard-card.scss
new file mode 100644
index 0000000..32f25c7
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-card.scss
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.tb-dashboard-assigned-customers {
+  display: block;
+  display: -webkit-box;
+  height: 34px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+  margin-bottom: 4px;
+}
+
diff --git a/ui/src/app/dashboard/dashboard-card.tpl.html b/ui/src/app/dashboard/dashboard-card.tpl.html
index 6367867..ed19fda 100644
--- a/ui/src/app/dashboard/dashboard-card.tpl.html
+++ b/ui/src/app/dashboard/dashboard-card.tpl.html
@@ -15,6 +15,6 @@
     limitations under the License.
 
 -->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'dashboard.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'dashboard.public' | translate}}</div>
+<div class="tb-small tb-dashboard-assigned-customers" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomersText">{{'dashboard.assignedToCustomers' | translate}}: '{{vm.item.assignedCustomersText}}'</div>
+<div class="tb-small" ng-show="vm.parentCtl.dashboardsScope === 'tenant' && vm.item.publicCustomerId">{{'dashboard.public' | translate}}</div>
 
diff --git a/ui/src/app/dashboard/dashboard-fieldset.tpl.html b/ui/src/app/dashboard/dashboard-fieldset.tpl.html
index a54f477..00ee554 100644
--- a/ui/src/app/dashboard/dashboard-fieldset.tpl.html
+++ b/ui/src/app/dashboard/dashboard-fieldset.tpl.html
@@ -19,24 +19,29 @@
 		   ng-show="!isEdit && dashboardScope === 'tenant'"
 		   class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button>
 <md-button ng-click="onMakePublic({event: $event})"
-		   ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer && !isPublic"
+		   ng-show="!isEdit && dashboardScope === 'tenant' && !dashboard.publicCustomerId"
 		   class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button>
-<md-button ng-click="onAssignToCustomer({event: $event})"
-		   ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer"
-		   class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>
-<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})"
-		   ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer"
-		   class="md-raised md-primary">{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }}</md-button>
+<md-button ng-click="onMakePrivate({event: $event})"
+		   ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
+		   						dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))"
+		   class="md-raised md-primary">{{ 'dashboard.make-private' | translate }}</md-button>
+<md-button ng-click="onManageAssignedCustomers({event: $event})"
+		   ng-show="!isEdit && dashboardScope === 'tenant'"
+		   class="md-raised md-primary">{{ 'dashboard.manage-assigned-customers' | translate }}</md-button>
+<md-button ng-click="onUnassignFromCustomer({event: $event})"
+		   ng-show="!isEdit && dashboardScope === 'customer' && customerId != dashboard.publicCustomerId"
+		   class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
 <md-button ng-click="onDeleteDashboard({event: $event})"
 		   ng-show="!isEdit && dashboardScope === 'tenant'"
 		   class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button>
 <md-content class="md-padding" layout="column">
 	<md-input-container class="md-block"
-						ng-show="!isEdit && isAssignedToCustomer && !isPublic && dashboardScope === 'tenant'">
-		<label translate>dashboard.assignedToCustomer</label>
-		<input ng-model="assignedCustomer.title" disabled>
+						ng-show="!isEdit && dashboard.assignedCustomersText && dashboardScope === 'tenant'">
+		<label translate>dashboard.assignedToCustomers</label>
+		<input ng-model="dashboard.assignedCustomersText" disabled>
 	</md-input-container>
-	<div layout="column" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')">
+	<div layout="column" ng-show="!isEdit && ((dashboardScope === 'tenant' && dashboard.publicCustomerId ||
+		   						               dashboardScope === 'customer' && customerId == dashboard.publicCustomerId))">
 		<tb-social-share-panel style="padding-bottom: 10px;"
 							   share-title="{{ 'dashboard.socialshare-title' | translate:{dashboardTitle: dashboard.title} }}"
 							   share-text="{{ 'dashboard.socialshare-text' | translate:{dashboardTitle: dashboard.title} }}"
diff --git a/ui/src/app/dashboard/dashboards.controller.js b/ui/src/app/dashboard/dashboards.controller.js
index d2887f8..af76b67 100644
--- a/ui/src/app/dashboard/dashboards.controller.js
+++ b/ui/src/app/dashboard/dashboards.controller.js
@@ -17,12 +17,14 @@
 
 import addDashboardTemplate from './add-dashboard.tpl.html';
 import dashboardCard from './dashboard-card.tpl.html';
-import assignToCustomerTemplate from './assign-to-customer.tpl.html';
 import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html';
 import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html';
+import manageAssignedCustomersTemplate from './manage-assigned-customers.tpl.html';
 
 /* eslint-enable import/no-unresolved, import/default */
 
+import './dashboard-card.scss';
+
 /*@ngInject*/
 export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) {
 
@@ -48,23 +50,8 @@ export function MakeDashboardPublicDialogController($mdDialog, $translate, toast
 export function DashboardCardController(types) {
 
     var vm = this;
-
     vm.types = types;
 
-    vm.isAssignedToCustomer = function() {
-        if (vm.item && vm.item.customerId && vm.parentCtl.dashboardsScope === 'tenant' &&
-            vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) {
-            return true;
-        }
-        return false;
-    }
-
-    vm.isPublic = function() {
-        if (vm.item && vm.item.assignedCustomer && vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomer.isPublic) {
-            return true;
-        }
-        return false;
-    }
 }
 
 /*@ngInject*/
@@ -135,8 +122,9 @@ export function DashboardsController(userService, dashboardService, customerServ
 
     vm.dashboardsScope = $state.$current.data.dashboardsType;
 
-    vm.assignToCustomer = assignToCustomer;
     vm.makePublic = makePublic;
+    vm.makePrivate = makePrivate;
+    vm.manageAssignedCustomers = manageAssignedCustomers;
     vm.unassignFromCustomer = unassignFromCustomer;
     vm.exportDashboard = exportDashboard;
 
@@ -155,6 +143,7 @@ export function DashboardsController(userService, dashboardService, customerServ
         }
 
         if (customerId) {
+            vm.customerId = customerId;
             vm.customerDashboardsTitle = $translate.instant('customer.dashboards');
             customerService.getShortCustomerInfo(customerId).then(
                 function success(info) {
@@ -167,7 +156,7 @@ export function DashboardsController(userService, dashboardService, customerServ
 
         if (vm.dashboardsScope === 'tenant') {
             fetchDashboardsFunction = function (pageLink) {
-                return dashboardService.getTenantDashboards(pageLink, true);
+                return dashboardService.getTenantDashboards(pageLink);
             };
             deleteDashboardFunction = function (dashboardId) {
                 return dashboardService.deleteDashboard(dashboardId);
@@ -194,11 +183,33 @@ export function DashboardsController(userService, dashboardService, customerServ
                     details: function() { return $translate.instant('dashboard.make-public') },
                     icon: "share",
                     isEnabled: function(dashboard) {
-                        return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
+                        return dashboard && !dashboard.publicCustomerId;
                     }
                 });
-
             dashboardActionsList.push({
+                onAction: function ($event, item) {
+                    makePrivate($event, item);
+                },
+                name: function() { return $translate.instant('action.make-private') },
+                details: function() { return $translate.instant('dashboard.make-private') },
+                icon: "reply",
+                isEnabled: function(dashboard) {
+                    return dashboard && dashboard.publicCustomerId;
+                }
+            });
+            dashboardActionsList.push({
+                onAction: function ($event, item) {
+                    manageAssignedCustomers($event, item);
+                },
+                name: function() { return $translate.instant('action.assign') },
+                details: function() { return $translate.instant('dashboard.manage-assigned-customers') },
+                icon: "assignment_ind",
+                isEnabled: function(dashboard) {
+                    return dashboard;
+                }
+            });
+
+            /*dashboardActionsList.push({
                     onAction: function ($event, item) {
                         assignToCustomer($event, [ item.id.id ]);
                     },
@@ -208,8 +219,8 @@ export function DashboardsController(userService, dashboardService, customerServ
                     isEnabled: function(dashboard) {
                         return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
                     }
-                });
-            dashboardActionsList.push({
+                });*/
+            /*dashboardActionsList.push({
                     onAction: function ($event, item) {
                         unassignFromCustomer($event, item, false);
                     },
@@ -219,18 +230,7 @@ export function DashboardsController(userService, dashboardService, customerServ
                     isEnabled: function(dashboard) {
                         return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic;
                     }
-                });
-            dashboardActionsList.push({
-                    onAction: function ($event, item) {
-                        unassignFromCustomer($event, item, true);
-                    },
-                    name: function() { return $translate.instant('action.make-private') },
-                    details: function() { return $translate.instant('dashboard.make-private') },
-                    icon: "reply",
-                    isEnabled: function(dashboard) {
-                        return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && dashboard.assignedCustomer.isPublic;
-                    }
-                });
+                });*/
 
             dashboardActionsList.push(
                 {
@@ -246,7 +246,7 @@ export function DashboardsController(userService, dashboardService, customerServ
             dashboardGroupActionsList.push(
                     {
                         onAction: function ($event, items) {
-                            assignDashboardsToCustomer($event, items);
+                            assignDashboardsToCustomers($event, items);
                         },
                         name: function() { return $translate.instant('dashboard.assign-dashboards') },
                         details: function(selectedCount) {
@@ -255,6 +255,17 @@ export function DashboardsController(userService, dashboardService, customerServ
                         icon: "assignment_ind"
                     }
             );
+            dashboardGroupActionsList.push(
+                {
+                    onAction: function ($event, items) {
+                        unassignDashboardsFromCustomers($event, items);
+                    },
+                    name: function() { return $translate.instant('dashboard.unassign-dashboards') },
+                    details: function(selectedCount) {
+                        return $translate.instant('dashboard.unassign-dashboards-action-text', {count: selectedCount}, "messageformat");
+                    },
+                    icon: "assignment_return"                }
+            );
 
             dashboardGroupActionsList.push(
                 {
@@ -290,10 +301,10 @@ export function DashboardsController(userService, dashboardService, customerServ
             });
         } else if (vm.dashboardsScope === 'customer' || vm.dashboardsScope === 'customer_user') {
             fetchDashboardsFunction = function (pageLink) {
-                return dashboardService.getCustomerDashboards(customerId, pageLink, true);
+                return dashboardService.getCustomerDashboards(customerId, pageLink);
             };
             deleteDashboardFunction = function (dashboardId) {
-                return dashboardService.unassignDashboardFromCustomer(dashboardId);
+                return dashboardService.unassignDashboardFromCustomer(customerId, dashboardId);
             };
             refreshDashboardsParamsFunction = function () {
                 return {"customerId": customerId, "topIndex": vm.topIndex};
@@ -314,26 +325,27 @@ export function DashboardsController(userService, dashboardService, customerServ
                 dashboardActionsList.push(
                     {
                         onAction: function ($event, item) {
-                            unassignFromCustomer($event, item, false);
+                            makePrivate($event, item);
                         },
-                        name: function() { return $translate.instant('action.unassign') },
-                        details: function() { return $translate.instant('dashboard.unassign-from-customer') },
-                        icon: "assignment_return",
+                        name: function() { return $translate.instant('action.make-private') },
+                        details: function() { return $translate.instant('dashboard.make-private') },
+                        icon: "reply",
                         isEnabled: function(dashboard) {
-                            return dashboard && !dashboard.assignedCustomer.isPublic;
+                            return dashboard && customerId == dashboard.publicCustomerId;
                         }
                     }
                 );
+
                 dashboardActionsList.push(
                     {
                         onAction: function ($event, item) {
-                            unassignFromCustomer($event, item, true);
+                            unassignFromCustomer($event, item, customerId);
                         },
-                        name: function() { return $translate.instant('action.make-private') },
-                        details: function() { return $translate.instant('dashboard.make-private') },
-                        icon: "reply",
+                        name: function() { return $translate.instant('action.unassign') },
+                        details: function() { return $translate.instant('dashboard.unassign-from-customer') },
+                        icon: "assignment_return",
                         isEnabled: function(dashboard) {
-                            return dashboard && dashboard.assignedCustomer.isPublic;
+                            return dashboard && customerId != dashboard.publicCustomerId;
                         }
                     }
                 );
@@ -341,7 +353,7 @@ export function DashboardsController(userService, dashboardService, customerServ
                 dashboardGroupActionsList.push(
                     {
                         onAction: function ($event, items) {
-                            unassignDashboardsFromCustomer($event, items);
+                            unassignDashboardsFromCustomer($event, items, customerId);
                         },
                         name: function() { return $translate.instant('dashboard.unassign-dashboards') },
                         details: function(selectedCount) {
@@ -351,7 +363,6 @@ export function DashboardsController(userService, dashboardService, customerServ
                     }
                 );
 
-
                 vm.dashboardGridConfig.addItemAction = {
                     onAction: function ($event) {
                         addDashboardsToCustomer($event);
@@ -428,39 +439,42 @@ export function DashboardsController(userService, dashboardService, customerServ
         return deferred.promise;
     }
 
-    function assignToCustomer($event, dashboardIds) {
+    function manageAssignedCustomers($event, dashboard) {
+        showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', dashboard.assignedCustomersIds);
+    }
+
+    function assignDashboardsToCustomers($event, items) {
+        var dashboardIds = [];
+        for (var id in items.selections) {
+            dashboardIds.push(id);
+        }
+        showManageAssignedCustomersDialog($event, dashboardIds, 'assign');
+    }
+
+    function unassignDashboardsFromCustomers($event, items) {
+        var dashboardIds = [];
+        for (var id in items.selections) {
+            dashboardIds.push(id);
+        }
+        showManageAssignedCustomersDialog($event, dashboardIds, 'unassign');
+    }
+
+    function showManageAssignedCustomersDialog($event, dashboardIds, actionType, assignedCustomers) {
         if ($event) {
             $event.stopPropagation();
         }
-        var pageSize = 10;
-        customerService.getCustomers({limit: pageSize, textSearch: ''}).then(
-            function success(_customers) {
-                var customers = {
-                    pageSize: pageSize,
-                    data: _customers.data,
-                    nextPageLink: _customers.nextPageLink,
-                    selection: null,
-                    hasNext: _customers.hasNext,
-                    pending: false
-                };
-                if (customers.hasNext) {
-                    customers.nextPageLink.limit = pageSize;
-                }
-                $mdDialog.show({
-                    controller: 'AssignDashboardToCustomerController',
-                    controllerAs: 'vm',
-                    templateUrl: assignToCustomerTemplate,
-                    locals: {dashboardIds: dashboardIds, customers: customers},
-                    parent: angular.element($document[0].body),
-                    fullscreen: true,
-                    targetEvent: $event
-                }).then(function () {
-                    vm.grid.refreshList();
-                }, function () {
-                });
-            },
-            function fail() {
-            });
+        $mdDialog.show({
+            controller: 'ManageAssignedCustomersController',
+            controllerAs: 'vm',
+            templateUrl: manageAssignedCustomersTemplate,
+            locals: {actionType: actionType, dashboardIds: dashboardIds, assignedCustomers: assignedCustomers},
+            parent: angular.element($document[0].body),
+            fullscreen: true,
+            targetEvent: $event
+        }).then(function () {
+            vm.grid.refreshList();
+        }, function () {
+        });
     }
 
     function addDashboardsToCustomer($event) {
@@ -468,7 +482,7 @@ export function DashboardsController(userService, dashboardService, customerServ
             $event.stopPropagation();
         }
         var pageSize = 10;
-        dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}, false).then(
+        dashboardService.getTenantDashboards({limit: pageSize, textSearch: ''}).then(
             function success(_dashboards) {
                 var dashboards = {
                     pageSize: pageSize,
@@ -499,30 +513,13 @@ export function DashboardsController(userService, dashboardService, customerServ
             });
     }
 
-    function assignDashboardsToCustomer($event, items) {
-        var dashboardIds = [];
-        for (var id in items.selections) {
-            dashboardIds.push(id);
-        }
-        assignToCustomer($event, dashboardIds);
-    }
-
-    function unassignFromCustomer($event, dashboard, isPublic) {
+    function unassignFromCustomer($event, dashboard, customerId) {
         if ($event) {
             $event.stopPropagation();
         }
-        var title;
-        var content;
-        var label;
-        if (isPublic) {
-            title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
-            content = $translate.instant('dashboard.make-private-dashboard-text');
-            label = $translate.instant('dashboard.make-private-dashboard');
-        } else {
-            title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
-            content = $translate.instant('dashboard.unassign-dashboard-text');
-            label = $translate.instant('dashboard.unassign-dashboard');
-        }
+        var title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title});
+        var content = $translate.instant('dashboard.unassign-dashboard-text');
+        var label = $translate.instant('dashboard.unassign-dashboard');
         var confirm = $mdDialog.confirm()
             .targetEvent($event)
             .title(title)
@@ -531,7 +528,7 @@ export function DashboardsController(userService, dashboardService, customerServ
             .cancel($translate.instant('action.no'))
             .ok($translate.instant('action.yes'));
         $mdDialog.show(confirm).then(function () {
-            dashboardService.unassignDashboardFromCustomer(dashboard.id.id).then(function success() {
+            dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).then(function success() {
                 vm.grid.refreshList();
             });
         });
@@ -556,12 +553,33 @@ export function DashboardsController(userService, dashboardService, customerServ
         });
     }
 
+    function makePrivate($event, dashboard) {
+        if ($event) {
+            $event.stopPropagation();
+        }
+        var title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title});
+        var content = $translate.instant('dashboard.make-private-dashboard-text');
+        var label = $translate.instant('dashboard.make-private-dashboard');
+        var confirm = $mdDialog.confirm()
+            .targetEvent($event)
+            .title(title)
+            .htmlContent(content)
+            .ariaLabel(label)
+            .cancel($translate.instant('action.no'))
+            .ok($translate.instant('action.yes'));
+        $mdDialog.show(confirm).then(function () {
+            dashboardService.makeDashboardPrivate(dashboard.id.id).then(function success() {
+                vm.grid.refreshList();
+            });
+        });
+    }
+
     function exportDashboard($event, dashboard) {
         $event.stopPropagation();
         importExport.exportDashboard(dashboard.id.id);
     }
 
-    function unassignDashboardsFromCustomer($event, items) {
+    function unassignDashboardsFromCustomer($event, items, customerId) {
         var confirm = $mdDialog.confirm()
             .targetEvent($event)
             .title($translate.instant('dashboard.unassign-dashboards-title', {count: items.selectedCount}, 'messageformat'))
@@ -572,7 +590,7 @@ export function DashboardsController(userService, dashboardService, customerServ
         $mdDialog.show(confirm).then(function () {
             var tasks = [];
             for (var id in items.selections) {
-                tasks.push(dashboardService.unassignDashboardFromCustomer(id));
+                tasks.push(dashboardService.unassignDashboardFromCustomer(customerId, id));
             }
             $q.all(tasks).then(function () {
                 vm.grid.refreshList();
diff --git a/ui/src/app/dashboard/dashboards.tpl.html b/ui/src/app/dashboard/dashboards.tpl.html
index 8149821..0528eaf 100644
--- a/ui/src/app/dashboard/dashboards.tpl.html
+++ b/ui/src/app/dashboard/dashboards.tpl.html
@@ -25,10 +25,12 @@
 			<tb-dashboard-details dashboard="vm.grid.operatingItem()"
 								  is-edit="vm.grid.detailsConfig.isDetailsEditMode"
 								  dashboard-scope="vm.dashboardsScope"
+								  customer-id="vm.customerId"
 								  the-form="vm.grid.detailsForm"
-								  on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])"
 								  on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)"
-								  on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)"
+								  on-make-private="vm.makePrivate(event, vm.grid.detailsConfig.currentItem)"
+								  on-manage-assigned-customers="vm.manageAssignedCustomers(event, vm.grid.detailsConfig.currentItem)"
+								  on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, vm.customerId)"
 								  on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)"
 								  on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details>
 		</md-tab>
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
index 4089716..ac63dc5 100644
--- a/ui/src/app/dashboard/index.js
+++ b/ui/src/app/dashboard/index.js
@@ -40,8 +40,8 @@ import DashboardRoutes from './dashboard.routes';
 import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller';
 import DashboardController from './dashboard.controller';
 import DashboardSettingsController from './dashboard-settings.controller';
-import AssignDashboardToCustomerController from './assign-to-customer.controller';
 import AddDashboardsToCustomerController from './add-dashboards-to-customer.controller';
+import ManageAssignedCustomersController from './manage-assigned-customers.controller';
 import AddWidgetController from './add-widget.controller';
 import DashboardDirective from './dashboard.directive';
 import EditWidgetDirective from './edit-widget.directive';
@@ -74,8 +74,8 @@ export default angular.module('thingsboard.dashboard', [
     .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController)
     .controller('DashboardController', DashboardController)
     .controller('DashboardSettingsController', DashboardSettingsController)
-    .controller('AssignDashboardToCustomerController', AssignDashboardToCustomerController)
     .controller('AddDashboardsToCustomerController', AddDashboardsToCustomerController)
+    .controller('ManageAssignedCustomersController', ManageAssignedCustomersController)
     .controller('AddWidgetController', AddWidgetController)
     .directive('tbDashboardDetails', DashboardDirective)
     .directive('tbEditWidget', EditWidgetDirective)
diff --git a/ui/src/app/dashboard/manage-assigned-customers.controller.js b/ui/src/app/dashboard/manage-assigned-customers.controller.js
new file mode 100644
index 0000000..90d3e6d
--- /dev/null
+++ b/ui/src/app/dashboard/manage-assigned-customers.controller.js
@@ -0,0 +1,69 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*@ngInject*/
+export default function ManageAssignedCustomersController($mdDialog, $q, types, dashboardService, actionType, dashboardIds, assignedCustomers) {
+
+    var vm = this;
+
+    vm.types = types;
+    vm.actionType = actionType;
+    vm.dashboardIds = dashboardIds;
+    vm.assignedCustomers = assignedCustomers;
+    if (actionType != 'manage') {
+        vm.assignedCustomers = [];
+    }
+
+    if (actionType == 'manage') {
+        vm.titleText = 'dashboard.manage-assigned-customers';
+        vm.labelText = 'dashboard.assigned-customers';
+        vm.actionName = 'action.update';
+    } else if (actionType == 'assign') {
+        vm.titleText = 'dashboard.assign-to-customers';
+        vm.labelText = 'dashboard.assign-to-customers-text';
+        vm.actionName = 'action.assign';
+    } else if (actionType == 'unassign') {
+        vm.titleText = 'dashboard.unassign-from-customers';
+        vm.labelText = 'dashboard.unassign-from-customers-text';
+        vm.actionName = 'action.unassign';
+    }
+
+    vm.submit = submit;
+    vm.cancel = cancel;
+
+    function cancel () {
+        $mdDialog.cancel();
+    }
+
+    function submit () {
+        var tasks = [];
+        for (var i=0;i<vm.dashboardIds.length;i++) {
+            var dashboardId = vm.dashboardIds[i];
+            var promise;
+            if (vm.actionType == 'manage') {
+                promise = dashboardService.updateDashboardCustomers(dashboardId, vm.assignedCustomers);
+            } else if (vm.actionType == 'assign') {
+                promise = dashboardService.addDashboardCustomers(dashboardId, vm.assignedCustomers);
+            } else if (vm.actionType == 'unassign') {
+                promise = dashboardService.removeDashboardCustomers(dashboardId, vm.assignedCustomers);
+            }
+            tasks.push(promise);
+        }
+        $q.all(tasks).then(function () {
+            $mdDialog.hide();
+        });
+    }
+
+}
diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js
index 62f3d6d..2d114db 100644
--- a/ui/src/app/entity/entity-autocomplete.directive.js
+++ b/ui/src/app/entity/entity-autocomplete.directive.js
@@ -38,7 +38,12 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
             if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
                 limit += scope.excludeEntityIds.length;
             }
-            entityService.getEntitiesByNameFilter(scope.entityType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) {
+            var targetType = scope.entityType;
+            if (targetType == types.aliasEntityType.current_customer) {
+                targetType = types.entityType.customer;
+            }
+
+            entityService.getEntitiesByNameFilter(targetType, searchText, limit, {ignoreLoading: true}, scope.entitySubtype).then(function success(result) {
                 if (result) {
                     if (scope.excludeEntityIds && scope.excludeEntityIds.length) {
                         var entities = [];
@@ -71,7 +76,11 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
 
         ngModelCtrl.$render = function () {
             if (ngModelCtrl.$viewValue) {
-                entityService.getEntity(scope.entityType, ngModelCtrl.$viewValue).then(
+                var targetType = scope.entityType;
+                if (targetType == types.aliasEntityType.current_customer) {
+                    targetType = types.entityType.customer;
+                }
+                entityService.getEntity(targetType, ngModelCtrl.$viewValue).then(
                     function success(entity) {
                         scope.entity = entity;
                     },
@@ -114,55 +123,61 @@ export default function EntityAutocomplete($compile, $templateCache, $q, $filter
                     scope.selectEntityText = 'asset.select-asset';
                     scope.entityText = 'asset.asset';
                     scope.noEntitiesMatchingText = 'asset.no-assets-matching';
-                    scope.entityRequiredText = 'asset.asset-required'
+                    scope.entityRequiredText = 'asset.asset-required';
                     break;
                 case types.entityType.device:
                     scope.selectEntityText = 'device.select-device';
                     scope.entityText = 'device.device';
                     scope.noEntitiesMatchingText = 'device.no-devices-matching';
-                    scope.entityRequiredText = 'device.device-required'
+                    scope.entityRequiredText = 'device.device-required';
                     break;
                 case types.entityType.rule:
                     scope.selectEntityText = 'rule.select-rule';
                     scope.entityText = 'rule.rule';
                     scope.noEntitiesMatchingText = 'rule.no-rules-matching';
-                    scope.entityRequiredText = 'rule.rule-required'
+                    scope.entityRequiredText = 'rule.rule-required';
                     break;
                 case types.entityType.plugin:
                     scope.selectEntityText = 'plugin.select-plugin';
                     scope.entityText = 'plugin.plugin';
                     scope.noEntitiesMatchingText = 'plugin.no-plugins-matching';
-                    scope.entityRequiredText = 'plugin.plugin-required'
+                    scope.entityRequiredText = 'plugin.plugin-required';
                     break;
                 case types.entityType.tenant:
                     scope.selectEntityText = 'tenant.select-tenant';
                     scope.entityText = 'tenant.tenant';
                     scope.noEntitiesMatchingText = 'tenant.no-tenants-matching';
-                    scope.entityRequiredText = 'tenant.tenant-required'
+                    scope.entityRequiredText = 'tenant.tenant-required';
                     break;
                 case types.entityType.customer:
                     scope.selectEntityText = 'customer.select-customer';
                     scope.entityText = 'customer.customer';
                     scope.noEntitiesMatchingText = 'customer.no-customers-matching';
-                    scope.entityRequiredText = 'customer.customer-required'
+                    scope.entityRequiredText = 'customer.customer-required';
                     break;
                 case types.entityType.user:
                     scope.selectEntityText = 'user.select-user';
                     scope.entityText = 'user.user';
                     scope.noEntitiesMatchingText = 'user.no-users-matching';
-                    scope.entityRequiredText = 'user.user-required'
+                    scope.entityRequiredText = 'user.user-required';
                     break;
                 case types.entityType.dashboard:
                     scope.selectEntityText = 'dashboard.select-dashboard';
                     scope.entityText = 'dashboard.dashboard';
                     scope.noEntitiesMatchingText = 'dashboard.no-dashboards-matching';
-                    scope.entityRequiredText = 'dashboard.dashboard-required'
+                    scope.entityRequiredText = 'dashboard.dashboard-required';
                     break;
                 case types.entityType.alarm:
                     scope.selectEntityText = 'alarm.select-alarm';
                     scope.entityText = 'alarm.alarm';
                     scope.noEntitiesMatchingText = 'alarm.no-alarms-matching';
-                    scope.entityRequiredText = 'alarm.alarm-required'
+                    scope.entityRequiredText = 'alarm.alarm-required';
+                    break;
+                case types.aliasEntityType.current_customer:
+                    scope.selectEntityText = 'customer.select-default-customer';
+                    scope.entityText = 'customer.default-customer';
+                    scope.noEntitiesMatchingText = 'customer.no-customers-matching';
+                    scope.entityRequiredText = 'customer.default-customer-required';
                     break;
             }
             if (scope.entity && scope.entity.id.entityType != scope.entityType) {
diff --git a/ui/src/app/entity/entity-filter.tpl.html b/ui/src/app/entity/entity-filter.tpl.html
index 9f62454..0e8ee11 100644
--- a/ui/src/app/entity/entity-filter.tpl.html
+++ b/ui/src/app/entity/entity-filter.tpl.html
@@ -32,6 +32,7 @@
         <tb-entity-select flex
                           the-form="theForm"
                           tb-required="true"
+                          use-alias-entity-types="true"
                           ng-model="filter.singleEntity">
         </tb-entity-select>
     </section>
@@ -78,6 +79,7 @@
             <tb-entity-select flex
                               the-form="theForm"
                               tb-required="false"
+                              use-alias-entity-types="true"
                               ng-model="filter.defaultStateEntity">
             </tb-entity-select>
         </div>
@@ -123,6 +125,7 @@
                               the-form="theForm"
                               tb-required="!filter.rootStateEntity"
                               ng-disabled="filter.rootStateEntity"
+                              use-alias-entity-types="true"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
         </div>
@@ -139,6 +142,7 @@
                 <tb-entity-select flex
                                   the-form="theForm"
                                   tb-required="false"
+                                  use-alias-entity-types="true"
                                   ng-model="filter.defaultStateEntity">
                 </tb-entity-select>
             </div>
@@ -182,6 +186,7 @@
                               the-form="theForm"
                               tb-required="!filter.rootStateEntity"
                               ng-disabled="filter.rootStateEntity"
+                              use-alias-entity-types="true"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
         </div>
@@ -198,6 +203,7 @@
                 <tb-entity-select flex
                                   the-form="theForm"
                                   tb-required="false"
+                                  use-alias-entity-types="true"
                                   ng-model="filter.defaultStateEntity">
                 </tb-entity-select>
             </div>
@@ -249,6 +255,7 @@
                               the-form="theForm"
                               tb-required="!filter.rootStateEntity"
                               ng-disabled="filter.rootStateEntity"
+                              use-alias-entity-types="true"
                               ng-model="filter.rootEntity">
             </tb-entity-select>
         </div>
@@ -265,6 +272,7 @@
                 <tb-entity-select flex
                                   the-form="theForm"
                                   tb-required="false"
+                                  use-alias-entity-types="true"
                                   ng-model="filter.defaultStateEntity">
                 </tb-entity-select>
             </div>
diff --git a/ui/src/app/entity/entity-select.directive.js b/ui/src/app/entity/entity-select.directive.js
index 2778a79..8e2fbf9 100644
--- a/ui/src/app/entity/entity-select.directive.js
+++ b/ui/src/app/entity/entity-select.directive.js
@@ -105,7 +105,8 @@ export default function EntitySelect($compile, $templateCache) {
         scope: {
             theForm: '=?',
             tbRequired: '=?',
-            disabled:'=ngDisabled'
+            disabled:'=ngDisabled',
+            useAliasEntityTypes: "=?"
         }
     };
 }
diff --git a/ui/src/app/entity/entity-select.tpl.html b/ui/src/app/entity/entity-select.tpl.html
index 13e17e7..d370202 100644
--- a/ui/src/app/entity/entity-select.tpl.html
+++ b/ui/src/app/entity/entity-select.tpl.html
@@ -20,6 +20,7 @@
                            the-form="theForm"
                            ng-disabled="disabled"
                            tb-required="tbRequired"
+                           use-alias-entity-types="useAliasEntityTypes"
                            ng-model="model.entityType">
     </tb-entity-type-select>
     <tb-entity-autocomplete flex ng-if="model.entityType"
diff --git a/ui/src/app/entity/entity-type-select.directive.js b/ui/src/app/entity/entity-type-select.directive.js
index 068c141..fd694c9 100644
--- a/ui/src/app/entity/entity-type-select.directive.js
+++ b/ui/src/app/entity/entity-type-select.directive.js
@@ -39,7 +39,7 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
 
         scope.ngModelCtrl = ngModelCtrl;
 
-        scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
+        scope.entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
 
         scope.typeName = function(type) {
             return type ? types.entityTypeTranslations[type].type : '';
@@ -79,7 +79,8 @@ export default function EntityTypeSelect($compile, $templateCache, utils, entity
             theForm: '=?',
             tbRequired: '=?',
             disabled:'=ngDisabled',
-            allowedEntityTypes: "=?"
+            allowedEntityTypes: "=?",
+            useAliasEntityTypes: "=?"
         }
     };
 }
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index 62e1bb3..8a5948c 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -540,7 +540,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
             function success(dashboard) {
                 var name = dashboard.title;
                 name = name.toLowerCase().replace(/\W/g,"_");
-                exportToPc(prepareExport(dashboard), name + '.json');
+                exportToPc(prepareDashboardExport(dashboard), name + '.json');
             },
             function fail(rejection) {
                 var message = rejection;
@@ -552,6 +552,15 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
         );
     }
 
+    function prepareDashboardExport(dashboard) {
+        dashboard = prepareExport(dashboard);
+        delete dashboard.assignedCustomers;
+        delete dashboard.publicCustomerId;
+        delete dashboard.assignedCustomersText;
+        delete dashboard.assignedCustomersIds;
+        return dashboard;
+    }
+
     function importDashboard($event) {
         var deferred = $q.defer();
         openImportDialog($event, 'dashboard.import', 'dashboard.dashboard-file').then(
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 96dc152..f66347c 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -383,7 +383,10 @@ export default angular.module('thingsboard.locale', [])
                     "idCopiedMessage": "Customer Id has been copied to clipboard",
                     "select-customer": "Select customer",
                     "no-customers-matching": "No customers matching '{{entity}}' were found.",
-                    "customer-required": "Customer is required"
+                    "customer-required": "Customer is required",
+                    "select-default-customer": "Select default customer",
+                    "default-customer": "Default customer",
+                    "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
                 },
                 "datetime": {
                     "date-from": "Date from",
@@ -404,6 +407,12 @@ export default angular.module('thingsboard.locale', [])
                     "unassign-from-customer": "Unassign from customer",
                     "make-public": "Make dashboard public",
                     "make-private": "Make dashboard private",
+                    "manage-assigned-customers": "Manage assigned customers",
+                    "assigned-customers": "Assigned customers",
+                    "assign-to-customers": "Assign Dashboard(s) To Customers",
+                    "assign-to-customers-text": "Please select the customers to assign the dashboard(s)",
+                    "unassign-from-customers": "Unassign Dashboard(s) From Customers",
+                    "unassign-from-customers-text": "Please select the customers to unassign from the dashboard(s)",
                     "no-dashboards-text": "No dashboards found",
                     "no-widgets": "No widgets configured",
                     "add-widget": "Add new widget",
@@ -418,7 +427,8 @@ export default angular.module('thingsboard.locale', [])
                     "add-dashboard-text": "Add new dashboard",
                     "assign-dashboards": "Assign dashboards",
                     "assign-new-dashboard": "Assign new dashboard",
-                    "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customer",
+                    "assign-dashboards-text": "Assign { count, select, 1 {1 dashboard} other {# dashboards} } to customers",
+                    "unassign-dashboards-action-text": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customers",
                     "delete-dashboards": "Delete dashboards",
                     "unassign-dashboards": "Unassign dashboards",
                     "unassign-dashboards-action-title": "Unassign { count, select, 1 {1 dashboard} other {# dashboards} } from customer",
@@ -500,6 +510,7 @@ export default angular.module('thingsboard.locale', [])
                                               "Please contact your administrator in order to resolve this issue.",
                     "select-devices": "Select devices",
                     "assignedToCustomer": "Assigned to customer",
+                    "assignedToCustomers": "Assigned to customers",
                     "public": "Public",
                     "public-link": "Public link",
                     "copy-public-link": "Copy public link",
@@ -735,6 +746,7 @@ export default angular.module('thingsboard.locale', [])
                     "type-alarms": "Alarms",
                     "list-of-alarms": "{ count, select, 1 {One alarms} other {List of # alarms} }",
                     "alarm-name-starts-with": "Alarms whose names start with '{{prefix}}'",
+                    "type-current-customer": "Current Customer",
                     "search": "Search entities",
                     "selected-entities": "{ count, select, 1 {1 entity} other {# entities} } selected",
                     "entity-name": "Entity name",