Browse Source

UI: Switch dashboards control. Import/export for plugins, rules, widgets bundles and widget types.

pull/78/head
Igor Kulikov 10 years ago
parent
commit
7d4da490fe
  1. 12
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  2. 5
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  3. 85
      application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java
  4. 73
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  5. 122
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  6. 20
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java
  7. 25
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDaoImpl.java
  8. 53
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
  9. 69
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDaoImpl.java
  10. 5
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  11. 45
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  12. 201
      dao/src/main/java/org/thingsboard/server/dao/model/DashboardInfoEntity.java
  13. 6
      dao/src/main/resources/schema.cql
  14. 4
      dao/src/main/resources/system-data.cql
  15. 55
      dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceImplTest.java
  16. 2
      ui/package.json
  17. 27
      ui/src/app/api/user.service.js
  18. 14
      ui/src/app/api/widget.service.js
  19. 127
      ui/src/app/components/dashboard-autocomplete.directive.js
  20. 30
      ui/src/app/components/dashboard-autocomplete.scss
  21. 43
      ui/src/app/components/dashboard-autocomplete.tpl.html
  22. 56
      ui/src/app/components/dashboard-select.directive.js
  23. 17
      ui/src/app/components/dashboard-select.scss
  24. 34
      ui/src/app/components/dashboard-select.tpl.html
  25. 13
      ui/src/app/components/grid.directive.js
  26. 34
      ui/src/app/components/widget.controller.js
  27. 4
      ui/src/app/dashboard/dashboard-card.tpl.html
  28. 21
      ui/src/app/dashboard/dashboard-fieldset.tpl.html
  29. 37
      ui/src/app/dashboard/dashboard.controller.js
  30. 22
      ui/src/app/dashboard/dashboard.directive.js
  31. 12
      ui/src/app/dashboard/dashboard.tpl.html
  32. 38
      ui/src/app/dashboard/dashboards.controller.js
  33. 2
      ui/src/app/dashboard/index.js
  34. 18
      ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js
  35. 7
      ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html
  36. 4
      ui/src/app/device/device-card.tpl.html
  37. 1
      ui/src/app/device/device.controller.js
  38. 2
      ui/src/app/device/index.js
  39. 298
      ui/src/app/import-export/import-export.service.js
  40. 4
      ui/src/app/layout/index.js
  41. 37
      ui/src/app/locale/locale.constant.js
  42. 15
      ui/src/app/plugin/plugin-fieldset.tpl.html
  43. 41
      ui/src/app/plugin/plugin.controller.js
  44. 1
      ui/src/app/plugin/plugin.directive.js
  45. 1
      ui/src/app/plugin/plugins.tpl.html
  46. 15
      ui/src/app/rule/rule-fieldset.tpl.html
  47. 40
      ui/src/app/rule/rule.controller.js
  48. 1
      ui/src/app/rule/rule.directive.js
  49. 1
      ui/src/app/rule/rules.tpl.html
  50. 2
      ui/src/app/user/user-fieldset.scss
  51. 9
      ui/src/app/user/user-fieldset.tpl.html
  52. 27
      ui/src/app/user/user.directive.js
  53. 22
      ui/src/app/widget/lib/google-map.js
  54. 2
      ui/src/app/widget/lib/map-widget.js
  55. 19
      ui/src/app/widget/widget-library.controller.js
  56. 41
      ui/src/app/widget/widget-library.tpl.html
  57. 7
      ui/src/app/widget/widgets-bundle-fieldset.tpl.html
  58. 41
      ui/src/app/widget/widgets-bundle.controller.js
  59. 1
      ui/src/app/widget/widgets-bundle.directive.js
  60. 4
      ui/src/app/widget/widgets-bundles.tpl.html
  61. 7
      ui/src/scss/main.scss

12
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -389,7 +389,17 @@ public abstract class BaseController {
protected RuleMetaData checkRule(RuleMetaData rule) throws ThingsboardException { protected RuleMetaData checkRule(RuleMetaData rule) throws ThingsboardException {
checkNotNull(rule); checkNotNull(rule);
checkTenantId(rule.getTenantId()); SecurityUser authUser = getCurrentUser();
TenantId tenantId = rule.getTenantId();
validateId(tenantId, "Incorrect tenantId " + tenantId);
if (authUser.getAuthority() != Authority.SYS_ADMIN) {
if (authUser.getTenantId() == null ||
!tenantId.getId().equals(ModelConstants.NULL_UUID) && !authUser.getTenantId().equals(tenantId)) {
throw new ThingsboardException("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED);
}
}
return rule; return rule;
} }

5
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -19,6 +19,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -118,7 +119,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<Dashboard> getTenantDashboards( public TextPageData<DashboardInfo> getTenantDashboards(
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset, @RequestParam(required = false) String idOffset,
@ -135,7 +136,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<Dashboard> getCustomerDashboards( public TextPageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@RequestParam int limit, @RequestParam int limit,
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,

85
application/src/test/java/org/thingsboard/server/controller/DashboardControllerTest.java

@ -24,10 +24,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
@ -43,7 +40,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
public class DashboardControllerTest extends AbstractControllerTest { public class DashboardControllerTest extends AbstractControllerTest {
private IdComparator<Dashboard> idComparator = new IdComparator<>(); private IdComparator<DashboardInfo> idComparator = new IdComparator<>();
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@ -203,18 +200,18 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindTenantDashboards() throws Exception { public void testFindTenantDashboards() throws Exception {
List<Dashboard> dashboards = new ArrayList<>(); List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<173;i++) { for (int i=0;i<173;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard"+i); dashboard.setTitle("Dashboard"+i);
dashboards.add(doPost("/api/dashboard", dashboard, Dashboard.class)); dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
} }
List<Dashboard> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(24); TextPageLink pageLink = new TextPageLink(24);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData()); loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -230,32 +227,32 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindTenantDashboardsByTitle() throws Exception { public void testFindTenantDashboardsByTitle() throws Exception {
String title1 = "Dashboard title 1"; String title1 = "Dashboard title 1";
List<Dashboard> dashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<134;i++) { for (int i=0;i<134;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix; String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboardsTitle1.add(doPost("/api/dashboard", dashboard, Dashboard.class)); dashboardsTitle1.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
} }
String title2 = "Dashboard title 2"; String title2 = "Dashboard title 2";
List<Dashboard> dashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<112;i++) { for (int i=0;i<112;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix; String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboardsTitle2.add(doPost("/api/dashboard", dashboard, Dashboard.class)); dashboardsTitle2.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
} }
List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(15, title1); TextPageLink pageLink = new TextPageLink(15, title1);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData()); loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -267,11 +264,11 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData()); loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -283,25 +280,25 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (Dashboard dashboard : loadedDashboardsTitle1) { for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/dashboard/"+dashboard.getId().getId().toString()) doDelete("/api/dashboard/"+dashboard.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title1); pageLink = new TextPageLink(4, title1);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Dashboard dashboard : loadedDashboardsTitle2) { for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/dashboard/"+dashboard.getId().getId().toString()) doDelete("/api/dashboard/"+dashboard.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -313,21 +310,21 @@ public class DashboardControllerTest extends AbstractControllerTest {
customer = doPost("/api/customer", customer, Customer.class); customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
List<Dashboard> dashboards = new ArrayList<>(); List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<173;i++) { for (int i=0;i<173;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard"+i); dashboard.setTitle("Dashboard"+i);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboards.add(doPost("/api/customer/" + customerId.getId().toString() dashboards.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)); + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
} }
List<Dashboard> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(21); TextPageLink pageLink = new TextPageLink(21);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData()); loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -348,7 +345,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1"; String title1 = "Dashboard title 1";
List<Dashboard> dashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<125;i++) { for (int i=0;i<125;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
@ -356,11 +353,11 @@ public class DashboardControllerTest extends AbstractControllerTest {
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle1.add(doPost("/api/customer/" + customerId.getId().toString() dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)); + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
} }
String title2 = "Dashboard title 2"; String title2 = "Dashboard title 2";
List<Dashboard> dashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i=0;i<143;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
@ -368,16 +365,16 @@ public class DashboardControllerTest extends AbstractControllerTest {
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle2.add(doPost("/api/customer/" + customerId.getId().toString() dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)); + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
} }
List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(18, title1); TextPageLink pageLink = new TextPageLink(18, title1);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData()); loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -389,11 +386,11 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(7, title2); pageLink = new TextPageLink(7, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData()); loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -405,25 +402,25 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (Dashboard dashboard : loadedDashboardsTitle1) { for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString()) doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(5, title1); pageLink = new TextPageLink(5, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Dashboard dashboard : loadedDashboardsTitle2) { for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString()) doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(9, title2); pageLink = new TextPageLink(9, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink); new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }

73
common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java

@ -15,19 +15,13 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
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 com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.DashboardId;
public class Dashboard extends SearchTextBased<DashboardId> { public class Dashboard extends DashboardInfo {
private static final long serialVersionUID = 872682138346187503L; private static final long serialVersionUID = 872682138346187503L;
private TenantId tenantId;
private CustomerId customerId;
private String title;
private JsonNode configuration; private JsonNode configuration;
public Dashboard() { public Dashboard() {
@ -37,37 +31,14 @@ public class Dashboard extends SearchTextBased<DashboardId> {
public Dashboard(DashboardId id) { public Dashboard(DashboardId id) {
super(id); super(id);
} }
public Dashboard(Dashboard dashboard) {
super(dashboard);
this.tenantId = dashboard.getTenantId();
this.customerId = dashboard.getCustomerId();
this.title = dashboard.getTitle();
this.configuration = dashboard.getConfiguration();
}
public TenantId getTenantId() {
return tenantId;
}
public void setTenantId(TenantId tenantId) {
this.tenantId = tenantId;
}
public CustomerId getCustomerId() { public Dashboard(DashboardInfo dashboardInfo) {
return customerId; super(dashboardInfo);
} }
public void setCustomerId(CustomerId customerId) { public Dashboard(Dashboard dashboard) {
this.customerId = customerId; super(dashboard);
} this.configuration = dashboard.getConfiguration();
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
} }
public JsonNode getConfiguration() { public JsonNode getConfiguration() {
@ -78,19 +49,11 @@ public class Dashboard extends SearchTextBased<DashboardId> {
this.configuration = configuration; this.configuration = configuration;
} }
@Override
public String getSearchText() {
return title;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
int result = super.hashCode(); int result = super.hashCode();
result = prime * result + ((configuration == null) ? 0 : configuration.hashCode()); result = prime * result + ((configuration == null) ? 0 : configuration.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; return result;
} }
@ -108,21 +71,6 @@ public class Dashboard extends SearchTextBased<DashboardId> {
return false; return false;
} else if (!configuration.equals(other.configuration)) } else if (!configuration.equals(other.configuration))
return false; return false;
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;
} else if (!tenantId.equals(other.tenantId))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true; return true;
} }
@ -130,15 +78,14 @@ public class Dashboard extends SearchTextBased<DashboardId> {
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("Dashboard [tenantId="); builder.append("Dashboard [tenantId=");
builder.append(tenantId); builder.append(getTenantId());
builder.append(", customerId="); builder.append(", customerId=");
builder.append(customerId); builder.append(getCustomerId());
builder.append(", title="); builder.append(", title=");
builder.append(title); builder.append(getTitle());
builder.append(", configuration="); builder.append(", configuration=");
builder.append(configuration); builder.append(configuration);
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }
} }

122
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -0,0 +1,122 @@
/**
* 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 org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
public class DashboardInfo extends SearchTextBased<DashboardId> {
private TenantId tenantId;
private CustomerId customerId;
private String title;
public DashboardInfo() {
super();
}
public DashboardInfo(DashboardId id) {
super(id);
}
public DashboardInfo(DashboardInfo dashboardInfo) {
super(dashboardInfo);
this.tenantId = dashboardInfo.getTenantId();
this.customerId = dashboardInfo.getCustomerId();
this.title = dashboardInfo.getTitle();
}
public TenantId getTenantId() {
return tenantId;
}
public void setTenantId(TenantId tenantId) {
this.tenantId = tenantId;
}
public CustomerId getCustomerId() {
return customerId;
}
public void setCustomerId(CustomerId customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String getSearchText() {
return title;
}
@Override
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;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!super.equals(obj))
return false;
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;
} else if (!tenantId.equals(other.tenantId))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
@Override
public String toString() {
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("]");
return builder.toString();
}
}

20
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.model.DashboardEntity; import org.thingsboard.server.dao.model.DashboardEntity;
import org.thingsboard.server.dao.model.DashboardInfoEntity;
/** /**
* The Interface DashboardDao. * The Interface DashboardDao.
@ -38,23 +39,4 @@ public interface DashboardDao extends Dao<DashboardEntity> {
*/ */
DashboardEntity save(Dashboard dashboard); DashboardEntity save(Dashboard dashboard);
/**
* Find dashboards by tenantId and page link.
*
* @param tenantId the tenantId
* @param pageLink the page link
* @return the list of dashboard objects
*/
List<DashboardEntity> findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find dashboards by tenantId, customerId and page link.
*
* @param tenantId the tenantId
* @param customerId the customerId
* @param pageLink the page link
* @return the list of dashboard objects
*/
List<DashboardEntity> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
} }

25
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDaoImpl.java

@ -35,6 +35,7 @@ import org.thingsboard.server.dao.AbstractSearchTextDao;
import org.thingsboard.server.dao.model.DashboardEntity; import org.thingsboard.server.dao.model.DashboardEntity;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.thingsboard.server.dao.model.DashboardInfoEntity;
@Component @Component
@Slf4j @Slf4j
@ -56,28 +57,4 @@ public class DashboardDaoImpl extends AbstractSearchTextDao<DashboardEntity> imp
return save(new DashboardEntity(dashboard)); return save(new DashboardEntity(dashboard));
} }
@Override
public List<DashboardEntity> findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find dashboards by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<DashboardEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Arrays.asList(eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId),
eq(DASHBOARD_CUSTOMER_ID_PROPERTY, NULL_UUID)),
pageLink);
log.trace("Found dashboards [{}] by tenantId [{}] and pageLink [{}]", dashboardEntities, tenantId, pageLink);
return dashboardEntities;
}
@Override
public List<DashboardEntity> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
List<DashboardEntity> 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 dashboardEntities;
}
} }

53
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java

@ -0,0 +1,53 @@
/**
* 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.dashboard;
import java.util.List;
import java.util.UUID;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.model.DashboardEntity;
import org.thingsboard.server.dao.model.DashboardInfoEntity;
/**
* The Interface DashboardInfoDao.
*
* @param <T> the generic type
*/
public interface DashboardInfoDao extends Dao<DashboardInfoEntity> {
/**
* Find dashboards by tenantId and page link.
*
* @param tenantId the tenantId
* @param pageLink the page link
* @return the list of dashboard objects
*/
List<DashboardInfoEntity> findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
* Find dashboards by tenantId, customerId and page link.
*
* @param tenantId the tenantId
* @param customerId the customerId
* @param pageLink the page link
* @return the list of dashboard objects
*/
List<DashboardInfoEntity> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
}

69
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDaoImpl.java

@ -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.
*/
package org.thingsboard.server.dao.dashboard;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.AbstractSearchTextDao;
import org.thingsboard.server.dao.model.DashboardInfoEntity;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
import static org.thingsboard.server.dao.model.ModelConstants.*;
@Component
@Slf4j
public class DashboardInfoDaoImpl extends AbstractSearchTextDao<DashboardInfoEntity> implements DashboardInfoDao {
@Override
protected Class<DashboardInfoEntity> getColumnFamilyClass() {
return DashboardInfoEntity.class;
}
@Override
protected String getColumnFamilyName() {
return DASHBOARD_COLUMN_FAMILY_NAME;
}
@Override
public List<DashboardInfoEntity> findDashboardsByTenantId(UUID tenantId, TextPageLink pageLink) {
log.debug("Try to find dashboards by tenantId [{}] and pageLink [{}]", tenantId, pageLink);
List<DashboardInfoEntity> dashboardEntities = findPageWithTextSearch(DASHBOARD_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Collections.singletonList(eq(DASHBOARD_TENANT_ID_PROPERTY, tenantId)),
pageLink);
log.trace("Found dashboards [{}] by tenantId [{}] and pageLink [{}]", dashboardEntities, tenantId, pageLink);
return dashboardEntities;
}
@Override
public List<DashboardInfoEntity> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink 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 dashboardEntities;
}
}

5
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.dashboard; package org.thingsboard.server.dao.dashboard;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -34,11 +35,11 @@ public interface DashboardService {
public void deleteDashboard(DashboardId dashboardId); public void deleteDashboard(DashboardId dashboardId);
public TextPageData<Dashboard> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink); public TextPageData<DashboardInfo> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink);
public void deleteDashboardsByTenantId(TenantId tenantId); public void deleteDashboardsByTenantId(TenantId tenantId);
public TextPageData<Dashboard> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink); public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); public void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId);

45
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -23,6 +23,7 @@ import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -30,9 +31,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity; import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.model.DashboardEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
@ -40,7 +39,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
@Service @Service
@ -49,7 +47,10 @@ public class DashboardServiceImpl implements DashboardService {
@Autowired @Autowired
private DashboardDao dashboardDao; private DashboardDao dashboardDao;
@Autowired
private DashboardInfoDao dashboardInfoDao;
@Autowired @Autowired
private TenantDao tenantDao; private TenantDao tenantDao;
@ -94,13 +95,13 @@ public class DashboardServiceImpl implements DashboardService {
} }
@Override @Override
public TextPageData<Dashboard> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink) { public TextPageData<DashboardInfo> findDashboardsByTenantId(TenantId tenantId, TextPageLink pageLink) {
log.trace("Executing findDashboardsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); log.trace("Executing findDashboardsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardEntity> dashboardEntities = dashboardDao.findDashboardsByTenantId(tenantId.getId(), pageLink); List<DashboardInfoEntity> dashboardEntities = dashboardInfoDao.findDashboardsByTenantId(tenantId.getId(), pageLink);
List<Dashboard> dashboards = convertDataList(dashboardEntities); List<DashboardInfo> dashboards = convertDataList(dashboardEntities);
return new TextPageData<Dashboard>(dashboards, pageLink); return new TextPageData<DashboardInfo>(dashboards, pageLink);
} }
@Override @Override
@ -111,14 +112,14 @@ public class DashboardServiceImpl implements DashboardService {
} }
@Override @Override
public TextPageData<Dashboard> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) { public TextPageData<DashboardInfo> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink) {
log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink);
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId); Validator.validateId(customerId, "Incorrect customerId " + customerId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink); Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardEntity> dashboardEntities = dashboardDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); List<DashboardInfoEntity> dashboardEntities = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
List<Dashboard> dashboards = convertDataList(dashboardEntities); List<DashboardInfo> dashboards = convertDataList(dashboardEntities);
return new TextPageData<Dashboard>(dashboards, pageLink); return new TextPageData<DashboardInfo>(dashboards, pageLink);
} }
@Override @Override
@ -158,21 +159,21 @@ public class DashboardServiceImpl implements DashboardService {
} }
}; };
private PaginatedRemover<TenantId, DashboardEntity> tenantDashboardsRemover = private PaginatedRemover<TenantId, DashboardInfoEntity> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardEntity>() { new PaginatedRemover<TenantId, DashboardInfoEntity>() {
@Override @Override
protected List<DashboardEntity> findEntities(TenantId id, TextPageLink pageLink) { protected List<DashboardInfoEntity> findEntities(TenantId id, TextPageLink pageLink) {
return dashboardDao.findDashboardsByTenantId(id.getId(), pageLink); return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
} }
@Override @Override
protected void removeEntity(DashboardEntity entity) { protected void removeEntity(DashboardInfoEntity entity) {
deleteDashboard(new DashboardId(entity.getId())); deleteDashboard(new DashboardId(entity.getId()));
} }
}; };
class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardEntity> { class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfoEntity> {
private TenantId tenantId; private TenantId tenantId;
@ -181,12 +182,12 @@ public class DashboardServiceImpl implements DashboardService {
} }
@Override @Override
protected List<DashboardEntity> findEntities(CustomerId id, TextPageLink pageLink) { protected List<DashboardInfoEntity> findEntities(CustomerId id, TextPageLink pageLink) {
return dashboardDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink); return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
} }
@Override @Override
protected void removeEntity(DashboardEntity entity) { protected void removeEntity(DashboardInfoEntity entity) {
unassignDashboardFromCustomer(new DashboardId(entity.getId())); unassignDashboardFromCustomer(new DashboardId(entity.getId()));
} }

201
dao/src/main/java/org/thingsboard/server/dao/model/DashboardInfoEntity.java

@ -0,0 +1,201 @@
/**
* 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.model;
import java.util.UUID;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
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.dao.model.type.JsonCodec;
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.datastax.driver.mapping.annotations.Transient;
import com.fasterxml.jackson.databind.JsonNode;
@Table(name = ModelConstants.DASHBOARD_COLUMN_FAMILY_NAME)
public class DashboardInfoEntity implements SearchTextEntity<DashboardInfo> {
@Transient
private static final long serialVersionUID = 2998395951247446191L;
@PartitionKey(value = 0)
@Column(name = ModelConstants.ID_PROPERTY)
private UUID id;
@PartitionKey(value = 1)
@Column(name = ModelConstants.DASHBOARD_TENANT_ID_PROPERTY)
private UUID tenantId;
@PartitionKey(value = 2)
@Column(name = ModelConstants.DASHBOARD_CUSTOMER_ID_PROPERTY)
private UUID customerId;
@Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY)
private String title;
@Column(name = ModelConstants.SEARCH_TEXT_PROPERTY)
private String searchText;
public DashboardInfoEntity() {
super();
}
public DashboardInfoEntity(DashboardInfo dashboardInfo) {
if (dashboardInfo.getId() != null) {
this.id = dashboardInfo.getId().getId();
}
if (dashboardInfo.getTenantId() != null) {
this.tenantId = dashboardInfo.getTenantId().getId();
}
if (dashboardInfo.getCustomerId() != null) {
this.customerId = dashboardInfo.getCustomerId().getId();
}
this.title = dashboardInfo.getTitle();
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public UUID getTenantId() {
return tenantId;
}
public void setTenantId(UUID tenantId) {
this.tenantId = tenantId;
}
public UUID getCustomerId() {
return customerId;
}
public void setCustomerId(UUID customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String getSearchTextSource() {
return title;
}
@Override
public void setSearchText(String searchText) {
this.searchText = searchText;
}
public String getSearchText() {
return searchText;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((customerId == null) ? 0 : customerId.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result + ((searchText == null) ? 0 : searchText.hashCode());
result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode());
result = prime * result + ((title == null) ? 0 : title.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DashboardInfoEntity other = (DashboardInfoEntity) obj;
if (customerId == null) {
if (other.customerId != null)
return false;
} else if (!customerId.equals(other.customerId))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (searchText == null) {
if (other.searchText != null)
return false;
} else if (!searchText.equals(other.searchText))
return false;
if (tenantId == null) {
if (other.tenantId != null)
return false;
} else if (!tenantId.equals(other.tenantId))
return false;
if (title == null) {
if (other.title != null)
return false;
} else if (!title.equals(other.title))
return false;
return true;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("DashboardInfoEntity [id=");
builder.append(id);
builder.append(", tenantId=");
builder.append(tenantId);
builder.append(", customerId=");
builder.append(customerId);
builder.append(", title=");
builder.append(title);
builder.append(", searchText=");
builder.append(searchText);
builder.append("]");
return builder.toString();
}
@Override
public DashboardInfo toData() {
DashboardInfo dashboardInfo = new DashboardInfo(new DashboardId(id));
dashboardInfo.setCreatedTime(UUIDs.unixTimestamp(id));
if (tenantId != null) {
dashboardInfo.setTenantId(new TenantId(tenantId));
}
if (customerId != null) {
dashboardInfo.setCustomerId(new CustomerId(customerId));
}
dashboardInfo.setTitle(title);
return dashboardInfo;
}
}

6
dao/src/main/resources/schema.cql

@ -244,14 +244,14 @@ CREATE TABLE IF NOT EXISTS thingsboard.dashboard (
search_text text, search_text text,
configuration text, configuration text,
PRIMARY KEY (id, tenant_id, customer_id) PRIMARY KEY (id, tenant_id, customer_id)
); );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
SELECT * SELECT *
from thingsboard.dashboard 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 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, customer_id, search_text, id ) PRIMARY KEY ( tenant_id, search_text, id, customer_id )
WITH CLUSTERING ORDER BY ( customer_id DESC, search_text ASC, id DESC ); 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 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
SELECT * SELECT *

4
dao/src/main/resources/system-data.cql

File diff suppressed because one or more lines are too long

55
dao/src/test/java/org/thingsboard/server/dao/service/DashboardServiceImplTest.java

@ -23,6 +23,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; 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.Tenant;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -38,7 +39,7 @@ import java.util.List;
public class DashboardServiceImplTest extends AbstractServiceTest { public class DashboardServiceImplTest extends AbstractServiceTest {
private IdComparator<Dashboard> idComparator = new IdComparator<>(); private IdComparator<DashboardInfo> idComparator = new IdComparator<>();
private TenantId tenantId; private TenantId tenantId;
@ -169,17 +170,17 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
TenantId tenantId = tenant.getId(); TenantId tenantId = tenant.getId();
List<Dashboard> dashboards = new ArrayList<>(); List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<165;i++) { for (int i=0;i<165;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
dashboard.setTitle("Dashboard"+i); dashboard.setTitle("Dashboard"+i);
dashboards.add(dashboardService.saveDashboard(dashboard)); dashboards.add(new DashboardInfo(dashboardService.saveDashboard(dashboard)));
} }
List<Dashboard> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(16); TextPageLink pageLink = new TextPageLink(16);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
loadedDashboards.addAll(pageData.getData()); loadedDashboards.addAll(pageData.getData());
@ -206,7 +207,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
@Test @Test
public void testFindDashboardsByTenantIdAndTitle() { public void testFindDashboardsByTenantIdAndTitle() {
String title1 = "Dashboard title 1"; String title1 = "Dashboard title 1";
List<Dashboard> dashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<123;i++) { for (int i=0;i<123;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
@ -214,10 +215,10 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
String title = title1+suffix; String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboardsTitle1.add(dashboardService.saveDashboard(dashboard)); dashboardsTitle1.add(new DashboardInfo(dashboardService.saveDashboard(dashboard)));
} }
String title2 = "Dashboard title 2"; String title2 = "Dashboard title 2";
List<Dashboard> dashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<193;i++) { for (int i=0;i<193;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
@ -225,12 +226,12 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
String title = title2+suffix; String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboardsTitle2.add(dashboardService.saveDashboard(dashboard)); dashboardsTitle2.add(new DashboardInfo(dashboardService.saveDashboard(dashboard)));
} }
List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(19, title1); TextPageLink pageLink = new TextPageLink(19, title1);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData()); loadedDashboardsTitle1.addAll(pageData.getData());
@ -244,7 +245,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink); pageData = dashboardService.findDashboardsByTenantId(tenantId, pageLink);
@ -259,7 +260,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (Dashboard dashboard : loadedDashboardsTitle1) { for (DashboardInfo dashboard : loadedDashboardsTitle1) {
dashboardService.deleteDashboard(dashboard.getId()); dashboardService.deleteDashboard(dashboard.getId());
} }
@ -268,7 +269,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Dashboard dashboard : loadedDashboardsTitle2) { for (DashboardInfo dashboard : loadedDashboardsTitle2) {
dashboardService.deleteDashboard(dashboard.getId()); dashboardService.deleteDashboard(dashboard.getId());
} }
@ -292,18 +293,18 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
customer = customerService.saveCustomer(customer); customer = customerService.saveCustomer(customer);
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
List<Dashboard> dashboards = new ArrayList<>(); List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<223;i++) { for (int i=0;i<223;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
dashboard.setTitle("Dashboard"+i); dashboard.setTitle("Dashboard"+i);
dashboard = dashboardService.saveDashboard(dashboard); dashboard = dashboardService.saveDashboard(dashboard);
dashboards.add(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)); dashboards.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
} }
List<Dashboard> loadedDashboards = new ArrayList<>(); List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23); TextPageLink pageLink = new TextPageLink(23);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
loadedDashboards.addAll(pageData.getData()); loadedDashboards.addAll(pageData.getData());
@ -337,7 +338,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1"; String title1 = "Dashboard title 1";
List<Dashboard> dashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<124;i++) { for (int i=0;i<124;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
@ -346,10 +347,10 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboard = dashboardService.saveDashboard(dashboard); dashboard = dashboardService.saveDashboard(dashboard);
dashboardsTitle1.add(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)); dashboardsTitle1.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
} }
String title2 = "Dashboard title 2"; String title2 = "Dashboard title 2";
List<Dashboard> dashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<151;i++) { for (int i=0;i<151;i++) {
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
@ -358,12 +359,12 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title); dashboard.setTitle(title);
dashboard = dashboardService.saveDashboard(dashboard); dashboard = dashboardService.saveDashboard(dashboard);
dashboardsTitle2.add(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)); dashboardsTitle2.add(new DashboardInfo(dashboardService.assignDashboardToCustomer(dashboard.getId(), customerId)));
} }
List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(24, title1); TextPageLink pageLink = new TextPageLink(24, title1);
TextPageData<Dashboard> pageData = null; TextPageData<DashboardInfo> pageData = null;
do { do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData()); loadedDashboardsTitle1.addAll(pageData.getData());
@ -377,7 +378,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>(); List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
@ -392,7 +393,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (Dashboard dashboard : loadedDashboardsTitle1) { for (DashboardInfo dashboard : loadedDashboardsTitle1) {
dashboardService.deleteDashboard(dashboard.getId()); dashboardService.deleteDashboard(dashboard.getId());
} }
@ -401,7 +402,7 @@ public class DashboardServiceImplTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Dashboard dashboard : loadedDashboardsTitle2) { for (DashboardInfo dashboard : loadedDashboardsTitle2) {
dashboardService.deleteDashboard(dashboard.getId()); dashboardService.deleteDashboard(dashboard.getId());
} }

2
ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard", "name": "thingsboard",
"private": true, "private": true,
"version": "1.2.0", "version": "1.2.1",
"description": "Thingsboard UI", "description": "Thingsboard UI",
"licenses": [ "licenses": [
{ {

27
ui/src/app/api/user.service.js

@ -22,9 +22,10 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
.name; .name;
/*@ngInject*/ /*@ngInject*/
function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelper, $translate, $state) { function UserService($http, $q, $rootScope, adminService, dashboardService, toast, store, jwtHelper, $translate, $state) {
var currentUser = null, var currentUser = null,
currentUserDetails = null, currentUserDetails = null,
allowedDashboardIds = [],
userLoaded = false; userLoaded = false;
var refreshTokenQueue = []; var refreshTokenQueue = [];
@ -90,6 +91,7 @@ function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelpe
function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) { function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) {
currentUser = null; currentUser = null;
currentUserDetails = null; currentUserDetails = null;
allowedDashboardIds = [];
if (!jwtToken) { if (!jwtToken) {
clearTokenData(); clearTokenData();
if (notify) { if (notify) {
@ -230,13 +232,28 @@ function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelpe
getUser(currentUser.userId).then( getUser(currentUser.userId).then(
function success(user) { function success(user) {
currentUserDetails = user; currentUserDetails = user;
$rootScope.forceFullscreen = false;
if (currentUserDetails.additionalInfo && if (currentUserDetails.additionalInfo &&
currentUserDetails.additionalInfo.defaultDashboardFullscreen) { currentUserDetails.additionalInfo.defaultDashboardFullscreen) {
$rootScope.forceFullscreen = currentUserDetails.additionalInfo.defaultDashboardFullscreen === true; $rootScope.forceFullscreen = currentUserDetails.additionalInfo.defaultDashboardFullscreen === true;
}
if ($rootScope.forceFullscreen && currentUser.authority === 'CUSTOMER_USER') {
var pageLink = {limit: 100};
dashboardService.getCustomerDashboards(currentUser.customerId, pageLink).then(
function success(result) {
var dashboards = result.data;
for (var d in dashboards) {
allowedDashboardIds.push(dashboards[d].id.id);
}
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
} else { } else {
$rootScope.forceFullscreen = false; deferred.resolve();
} }
deferred.resolve();
}, },
function fail() { function fail() {
deferred.reject(); deferred.reject();
@ -362,7 +379,9 @@ function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelpe
if ($rootScope.forceFullscreen) { if ($rootScope.forceFullscreen) {
if (to.name === 'home.profile') { if (to.name === 'home.profile') {
return false; return false;
} else if (to.name !== 'home.dashboards.dashboard' && params.dashboardId !== currentUserDetails.additionalInfo.defaultDashboardId) { } else if (to.name === 'home.dashboards.dashboard' && allowedDashboardIds.indexOf(params.dashboardId) > -1) {
return false;
} else {
return true; return true;
} }
} }

14
ui/src/app/api/widget.service.js

@ -92,6 +92,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
getInstantWidgetInfo: getInstantWidgetInfo, getInstantWidgetInfo: getInstantWidgetInfo,
deleteWidgetType: deleteWidgetType, deleteWidgetType: deleteWidgetType,
saveWidgetType: saveWidgetType, saveWidgetType: saveWidgetType,
saveImportedWidgetType: saveImportedWidgetType,
getWidgetType: getWidgetType, getWidgetType: getWidgetType,
getWidgetTypeById: getWidgetTypeById, getWidgetTypeById: getWidgetTypeById,
toWidgetInfo: toWidgetInfo toWidgetInfo: toWidgetInfo
@ -683,6 +684,19 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
return deferred.promise; return deferred.promise;
} }
function saveImportedWidgetType(widgetType) {
var deferred = $q.defer();
var url = '/api/widgetType';
$http.post(url, widgetType).then(function success(response) {
var widgetType = response.data;
deleteWidgetInfoFromCache(widgetType.bundleAlias, widgetType.alias, widgetType.tenantId.id === types.id.nullUid);
deferred.resolve(widgetType);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
function loadWidgetResources(widgetInfo, bundleAlias, isSystem) { function loadWidgetResources(widgetInfo, bundleAlias, isSystem) {
var deferred = $q.defer(); var deferred = $q.defer();

127
ui/src/app/components/dashboard-autocomplete.directive.js

@ -0,0 +1,127 @@
/*
* 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.
*/
import './dashboard-autocomplete.scss';
import thingsboardApiDashboard from '../api/dashboard.service';
import thingsboardApiUser from '../api/user.service';
/* eslint-disable import/no-unresolved, import/default */
import dashboardAutocompleteTemplate from './dashboard-autocomplete.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.dashboardAutocomplete', [thingsboardApiDashboard, thingsboardApiUser])
.directive('tbDashboardAutocomplete', DashboardAutocomplete)
.name;
/*@ngInject*/
function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, userService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(dashboardAutocompleteTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.dashboard = null;
scope.dashboardSearchText = '';
scope.fetchDashboards = function(searchText) {
var pageLink = {limit: 50, textSearch: searchText};
var deferred = $q.defer();
var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
if (scope.customerId) {
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink);
} else {
promise = $q.when({data: []});
}
} else {
promise = dashboardService.getTenantDashboards(pageLink);
}
promise.then(function success(result) {
deferred.resolve(result.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
}
scope.dashboardSearchTextChanged = function() {
}
scope.updateView = function () {
if (!scope.disabled) {
ngModelCtrl.$setViewValue(scope.dashboard ? scope.dashboard.id.id : null);
}
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
dashboardService.getDashboard(ngModelCtrl.$viewValue).then(
function success(dashboard) {
scope.dashboard = dashboard;
},
function fail() {
scope.dashboard = null;
}
);
} else {
scope.dashboard = null;
}
}
scope.$watch('dashboard', function () {
scope.updateView();
});
scope.$watch('disabled', function () {
scope.updateView();
});
if (scope.selectFirstDashboard) {
var pageLink = {limit: 1, textSearch: ''};
scope.dashboardFetchFunction(pageLink).then(function success(result) {
var dashboards = result.data;
if (dashboards.length > 0) {
scope.dashboard = dashboards[0];
}
}, function fail() {
});
}
$compile(element.contents())(scope);
}
return {
restrict: "E",
require: "^ngModel",
link: linker,
scope: {
dashboardsScope: '@',
customerId: '=',
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled',
selectFirstDashboard: '='
}
};
}

30
ui/src/app/components/dashboard-autocomplete.scss

@ -0,0 +1,30 @@
/**
* 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-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
}
.tb-dashboard-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;
}
}

43
ui/src/app/components/dashboard-autocomplete.tpl.html

@ -0,0 +1,43 @@
<!--
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.
-->
<md-autocomplete ng-required="tbRequired"
ng-disabled="disabled"
md-input-name="dashboard"
ng-model="dashboard"
md-selected-item="dashboard"
md-search-text="dashboardSearchText"
md-search-text-change="dashboardSearchTextChanged()"
md-items="item in fetchDashboards(dashboardSearchText)"
md-item-text="item.title"
md-min-length="0"
placeholder="{{ 'dashboard.select-dashboard' | translate }}"
md-menu-class="tb-dashboard-autocomplete">
<md-item-template>
<div class="tb-dashboard-item">
<span md-highlight-text="dashboardSearchText" md-highlight-flags="^i">{{item.title}}</span>
</div>
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<span translate translate-values='{ dashboard: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
</div>
</md-not-found>
<div ng-messages="theForm.dashboard.$error">
<div translate ng-message="required">dashboard.dashboard-required</div>
</div>
</md-autocomplete>

56
ui/src/app/components/dashboard-select.directive.js

@ -30,67 +30,50 @@ export default angular.module('thingsboard.directives.dashboardSelect', [thingsb
.name; .name;
/*@ngInject*/ /*@ngInject*/
function DashboardSelect($compile, $templateCache, $q, dashboardService, userService) { function DashboardSelect($compile, $templateCache, $q, types, dashboardService, userService) {
var linker = function (scope, element, attrs, ngModelCtrl) { var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(dashboardSelectTemplate); var template = $templateCache.get(dashboardSelectTemplate);
element.html(template); element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.dashboard = null; scope.dashboardId = null;
scope.dashboardSearchText = '';
scope.fetchDashboards = function(searchText) { var pageLink = {limit: 100};
var pageLink = {limit: 10, textSearch: searchText};
var deferred = $q.defer(); var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
var promise; if (scope.customerId && scope.customerId != types.id.nullUid) {
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink); promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink);
} else { } else {
promise = dashboardService.getTenantDashboards(pageLink); promise = $q.when({data: []});
} }
} else {
promise.then(function success(result) { promise = dashboardService.getTenantDashboards(pageLink);
deferred.resolve(result.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
} }
scope.dashboardSearchTextChanged = function() { promise.then(function success(result) {
} scope.dashboards = result.data;
}, function fail() {
scope.dashboards = [];
});
scope.updateView = function () { scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.dashboard); ngModelCtrl.$setViewValue(scope.dashboardId);
} }
ngModelCtrl.$render = function () { ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) { if (ngModelCtrl.$viewValue) {
scope.dashboard = ngModelCtrl.$viewValue; scope.dashboardId = ngModelCtrl.$viewValue;
} else { } else {
scope.dashboard = null; scope.dashboardId = null;
} }
} }
scope.$watch('dashboard', function () { scope.$watch('dashboardId', function () {
scope.updateView(); scope.updateView();
}); });
if (scope.selectFirstDashboard) {
var pageLink = {limit: 1, textSearch: ''};
scope.dashboardFetchFunction(pageLink).then(function success(result) {
var dashboards = result.data;
if (dashboards.length > 0) {
scope.dashboard = dashboards[0];
}
}, function fail() {
});
}
$compile(element.contents())(scope); $compile(element.contents())(scope);
} }
@ -101,9 +84,8 @@ function DashboardSelect($compile, $templateCache, $q, dashboardService, userSer
scope: { scope: {
dashboardsScope: '@', dashboardsScope: '@',
customerId: '=', customerId: '=',
theForm: '=?',
tbRequired: '=?', tbRequired: '=?',
selectFirstDashboard: '=' disabled:'=ngDisabled'
} }
}; };
} }

17
ui/src/app/components/dashboard-select.scss

@ -13,18 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
.tb-dashboard-autocomplete {
.tb-not-found { tb-dashboard-select {
display: block; md-select {
line-height: 1.5; pointer-events: all;
height: 48px;
}
.tb-dashboard-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;
} }
} }

34
ui/src/app/components/dashboard-select.tpl.html

@ -15,28 +15,12 @@
limitations under the License. limitations under the License.
--> -->
<md-autocomplete ng-required="tbRequired"
md-input-name="dashboard" <md-select ng-required="tbRequired"
ng-model="dashboard" ng-disabled="disabled"
md-selected-item="dashboard" ng-model="dashboardId"
md-search-text="dashboardSearchText" aria-label="{{ 'dashboard.dashboard' | translate }}">
md-search-text-change="dashboardSearchTextChanged()" <md-option ng-repeat="dashboard in dashboards" ng-value="dashboard.id.id">
md-items="item in fetchDashboards(dashboardSearchText)" {{dashboard.title}}
md-item-text="item.title" </md-option>
md-min-length="0" </md-select>
placeholder="{{ 'dashboard.select-dashboard' | translate }}"
md-menu-class="tb-dashboard-autocomplete">
<md-item-template>
<div class="tb-dashboard-item">
<span md-highlight-text="dashboardSearchText" md-highlight-flags="^i">{{item.title}}</span>
</div>
</md-item-template>
<md-not-found>
<div class="tb-not-found">
<span translate translate-values='{ dashboard: dashboardSearchText }'>dashboard.no-dashboards-matching</span>
</div>
</md-not-found>
<div ng-messages="theForm.dashboard.$error">
<div translate ng-message="required">dashboard.dashboard-required</div>
</div>
</md-autocomplete>

13
ui/src/app/components/grid.directive.js

@ -269,6 +269,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
return $q.when([]); return $q.when([]);
}; };
vm.loadItemDetailsFunc = vm.config.loadItemDetailsFunc || function (item) {
return $q.when(item);
};
vm.saveItemFunc = vm.config.saveItemFunc || function (item) { vm.saveItemFunc = vm.config.saveItemFunc || function (item) {
return $q.when(item); return $q.when(item);
}; };
@ -423,9 +427,12 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
return; return;
} }
} }
vm.detailsConfig.currentItem = item; vm.loadItemDetailsFunc(item).then(function success(detailsItem) {
vm.detailsConfig.isDetailsEditMode = false; detailsItem.index = item.index;
vm.detailsConfig.isDetailsOpen = true; vm.detailsConfig.currentItem = detailsItem;
vm.detailsConfig.isDetailsEditMode = false;
vm.detailsConfig.isDetailsOpen = true;
});
} }
function isCurrentItem(item) { function isCurrentItem(item) {

34
ui/src/app/components/widget.controller.js

@ -43,7 +43,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
var targetDeviceId = null; var targetDeviceId = null;
var originalTimewindow = null; var originalTimewindow = null;
var subscriptionTimewindow = null; var subscriptionTimewindow = null;
var dataUpdateCaf = null; var cafs = {};
var varsRegex = /\$\{([^\}]*)\}/g; var varsRegex = /\$\{([^\}]*)\}/g;
@ -186,11 +186,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function onDataUpdated() { function onDataUpdated() {
if (widgetContext.inited) { if (widgetContext.inited) {
if (dataUpdateCaf) { if (cafs['dataUpdate']) {
dataUpdateCaf(); cafs['dataUpdate']();
dataUpdateCaf = null; cafs['dataUpdate'] = null;
} }
dataUpdateCaf = tbRaf(function() { cafs['dataUpdate'] = tbRaf(function() {
try { try {
widgetTypeInstance.onDataUpdated(); widgetTypeInstance.onDataUpdated();
} catch (e) { } catch (e) {
@ -222,7 +222,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function onResize() { function onResize() {
if (checkSize()) { if (checkSize()) {
if (widgetContext.inited) { if (widgetContext.inited) {
tbRaf(function() { if (cafs['resize']) {
cafs['resize']();
cafs['resize'] = null;
}
cafs['resize'] = tbRaf(function() {
try { try {
widgetTypeInstance.onResize(); widgetTypeInstance.onResize();
} catch (e) { } catch (e) {
@ -251,7 +255,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (widgetContext.isEdit != isEdit) { if (widgetContext.isEdit != isEdit) {
widgetContext.isEdit = isEdit; widgetContext.isEdit = isEdit;
if (widgetContext.inited) { if (widgetContext.inited) {
tbRaf(function() { if (cafs['editMode']) {
cafs['editMode']();
cafs['editMode'] = null;
}
cafs['editMode'] = tbRaf(function() {
try { try {
widgetTypeInstance.onEditModeChanged(); widgetTypeInstance.onEditModeChanged();
} catch (e) { } catch (e) {
@ -266,7 +274,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (widgetContext.isMobile != isMobile) { if (widgetContext.isMobile != isMobile) {
widgetContext.isMobile = isMobile; widgetContext.isMobile = isMobile;
if (widgetContext.inited) { if (widgetContext.inited) {
tbRaf(function() { if (cafs['mobileMode']) {
cafs['mobileMode']();
cafs['mobileMode'] = null;
}
cafs['mobileMode'] = tbRaf(function() {
try { try {
widgetTypeInstance.onMobileModeChanged(); widgetTypeInstance.onMobileModeChanged();
} catch (e) { } catch (e) {
@ -282,6 +294,12 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (widgetContext.inited) { if (widgetContext.inited) {
widgetContext.inited = false; widgetContext.inited = false;
widgetContext.dataUpdatePending = false; widgetContext.dataUpdatePending = false;
for (var cafId in cafs) {
if (cafs[cafId]) {
cafs[cafId]();
cafs[cafId] = null;
}
}
try { try {
widgetTypeInstance.onDestroy(); widgetTypeInstance.onDestroy();
} catch (e) { } catch (e) {

4
ui/src/app/dashboard/dashboard-card.tpl.html

@ -15,4 +15,6 @@
limitations under the License. limitations under the License.
--> -->
<div></div> <div class="tb-small" ng-if="item &&
item.customerId.id != parentCtl.types.id.nullUid &&
parentCtl.dashboardsScope === 'tenant'" translate>dashboard.assignedToCustomer</div>

21
ui/src/app/dashboard/dashboard-fieldset.tpl.html

@ -15,12 +15,25 @@
limitations under the License. limitations under the License.
--> -->
<md-button ng-click="onAssignToCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button> <md-button ng-click="onAssignToCustomer({event: $event})"
<md-button ng-click="onUnassignFromCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'customer'" class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button> ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer"
<md-button ng-click="onExportDashboard({event: $event})" ng-show="!isEdit && dashboardScope === 'tenant'" class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> class="md-raised md-primary">{{ 'dashboard.assign-to-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-button ng-click="onUnassignFromCustomer({event: $event})"
ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer"
class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button>
<md-button ng-click="onExportDashboard({event: $event})"
ng-show="!isEdit && dashboardScope === 'tenant'"
class="md-raised md-primary">{{ 'dashboard.export' | 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-content class="md-padding" layout="column">
<md-input-container class="md-block"
ng-show="isAssignedToCustomer && dashboardScope === 'tenant'">
<label translate>dashboard.assignedToCustomer</label>
<input ng-model="assignedCustomer.title" disabled>
</md-input-container>
<fieldset ng-disabled="loading || !isEdit"> <fieldset ng-disabled="loading || !isEdit">
<md-input-container class="md-block"> <md-input-container class="md-block">
<label translate>dashboard.title</label> <label translate>dashboard.title</label>

37
ui/src/app/dashboard/dashboard.controller.js

@ -26,10 +26,9 @@ export default function DashboardController(types, widgetService, userService,
dashboardService, timeService, deviceService, itembuffer, importExport, hotkeys, $window, $rootScope, dashboardService, timeService, deviceService, itembuffer, importExport, hotkeys, $window, $rootScope,
$scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) { $scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
var user = userService.getCurrentUser();
var vm = this; var vm = this;
vm.user = userService.getCurrentUser();
vm.dashboard = null; vm.dashboard = null;
vm.editingWidget = null; vm.editingWidget = null;
vm.editingWidgetOriginal = null; vm.editingWidgetOriginal = null;
@ -49,6 +48,15 @@ export default function DashboardController(types, widgetService, userService,
vm.isToolbarOpened = false; vm.isToolbarOpened = false;
vm.currentDashboardId = $stateParams.dashboardId;
if ($stateParams.customerId) {
vm.currentCustomerId = $stateParams.customerId;
vm.currentDashboardScope = 'customer';
} else {
vm.currentDashboardScope = vm.user.authority === 'TENANT_ADMIN' ? 'tenant' : 'customer';
vm.currentCustomerId = vm.user.customerId;
}
Object.defineProperty(vm, 'toolbarOpened', { Object.defineProperty(vm, 'toolbarOpened', {
get: function() { return vm.isToolbarOpened || vm.isEdit; }, get: function() { return vm.isToolbarOpened || vm.isEdit; },
set: function() { } set: function() { }
@ -75,6 +83,7 @@ export default function DashboardController(types, widgetService, userService,
vm.prepareDashboardContextMenu = prepareDashboardContextMenu; vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
vm.prepareWidgetContextMenu = prepareWidgetContextMenu; vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
vm.editWidget = editWidget; vm.editWidget = editWidget;
vm.exportDashboard = exportDashboard;
vm.exportWidget = exportWidget; vm.exportWidget = exportWidget;
vm.importWidget = importWidget; vm.importWidget = importWidget;
vm.isTenantAdmin = isTenantAdmin; vm.isTenantAdmin = isTenantAdmin;
@ -104,6 +113,20 @@ export default function DashboardController(types, widgetService, userService,
} }
}); });
$scope.$watch('vm.currentDashboardId', function (newVal, prevVal) {
if (newVal !== prevVal && !vm.widgetEditMode) {
if (vm.currentDashboardScope === 'customer' && vm.user.authority === 'TENANT_ADMIN') {
$state.go('home.customers.dashboards.dashboard', {
customerId: vm.currentCustomerId,
dashboardId: vm.currentDashboardId
});
} else {
$state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
}
}
});
function loadWidgetLibrary() { function loadWidgetLibrary() {
vm.latestWidgetTypes = []; vm.latestWidgetTypes = [];
vm.timeseriesWidgetTypes = []; vm.timeseriesWidgetTypes = [];
@ -251,11 +274,11 @@ export default function DashboardController(types, widgetService, userService,
} }
function isTenantAdmin() { function isTenantAdmin() {
return user.authority === 'TENANT_ADMIN'; return vm.user.authority === 'TENANT_ADMIN';
} }
function isSystemAdmin() { function isSystemAdmin() {
return user.authority === 'SYS_ADMIN'; return vm.user.authority === 'SYS_ADMIN';
} }
function noData() { function noData() {
@ -267,7 +290,7 @@ export default function DashboardController(types, widgetService, userService,
} }
function showDashboardToolbar() { function showDashboardToolbar() {
return vm.dashboardInitComplete && !vm.configurationError; return vm.dashboardInitComplete;
} }
function openDeviceAliases($event) { function openDeviceAliases($event) {
@ -347,6 +370,10 @@ export default function DashboardController(types, widgetService, userService,
} }
} }
} }
function exportDashboard($event) {
$event.stopPropagation();
importExport.exportDashboard(vm.currentDashboardId);
}
function exportWidget($event, widget) { function exportWidget($event, widget) {
$event.stopPropagation(); $event.stopPropagation();

22
ui/src/app/dashboard/dashboard.directive.js

@ -20,10 +20,30 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function DashboardDirective($compile, $templateCache) { export default function DashboardDirective($compile, $templateCache, types, customerService) {
var linker = function (scope, element) { var linker = function (scope, element) {
var template = $templateCache.get(dashboardFieldsetTemplate); var template = $templateCache.get(dashboardFieldsetTemplate);
element.html(template); element.html(template);
scope.isAssignedToCustomer = false;
scope.assignedCustomer = null;
scope.$watch('dashboard', function(newVal) {
if (newVal) {
if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) {
scope.isAssignedToCustomer = true;
customerService.getCustomer(scope.dashboard.customerId.id).then(
function success(customer) {
scope.assignedCustomer = customer;
}
);
} else {
scope.isAssignedToCustomer = false;
scope.assignedCustomer = null;
}
}
});
$compile(element.contents())(scope); $compile(element.contents())(scope);
} }
return { return {

12
ui/src/app/dashboard/dashboard.tpl.html

@ -49,6 +49,13 @@
</md-button> </md-button>
<tb-user-menu ng-show="forceFullscreen" display-user-info="true"> <tb-user-menu ng-show="forceFullscreen" display-user-info="true">
</tb-user-menu> </tb-user-menu>
<md-button aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
ng-click="vm.exportDashboard($event)">
<md-tooltip md-direction="bottom">
{{ 'dashboard.export' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
</md-button>
<tb-timewindow is-toolbar direction="left" tooltip-direction="bottom" aggregation ng-model="vm.dashboardConfiguration.timewindow"> <tb-timewindow is-toolbar direction="left" tooltip-direction="bottom" aggregation ng-model="vm.dashboardConfiguration.timewindow">
</tb-timewindow> </tb-timewindow>
<tb-aliases-device-select ng-show="!vm.isEdit" <tb-aliases-device-select ng-show="!vm.isEdit"
@ -70,6 +77,11 @@
</md-tooltip> </md-tooltip>
<md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon> <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
</md-button> </md-button>
<tb-dashboard-select ng-show="!vm.isEdit && !vm.widgetEditMode"
ng-model="vm.currentDashboardId"
dashboards-scope="{{vm.currentDashboardScope}}"
customer-id="vm.currentCustomerId">
</tb-dashboard-select>
</md-fab-actions> </md-fab-actions>
</md-toolbar> </md-toolbar>
</md-fab-toolbar> </md-fab-toolbar>

38
ui/src/app/dashboard/dashboards.controller.js

@ -23,7 +23,8 @@ import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.ht
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function DashboardsController(userService, dashboardService, customerService, importExport, $scope, $controller, $state, $stateParams, $mdDialog, $document, $q, $translate) { export default function DashboardsController(userService, dashboardService, customerService, importExport, types, $scope, $controller,
$state, $stateParams, $mdDialog, $document, $q, $translate) {
var customerId = $stateParams.customerId; var customerId = $stateParams.customerId;
@ -41,6 +42,7 @@ export default function DashboardsController(userService, dashboardService, cust
var dashboardGroupActionsList = []; var dashboardGroupActionsList = [];
var vm = this; var vm = this;
vm.types = types;
vm.dashboardGridConfig = { vm.dashboardGridConfig = {
deleteItemTitleFunc: deleteDashboardTitle, deleteItemTitleFunc: deleteDashboardTitle,
@ -49,12 +51,15 @@ export default function DashboardsController(userService, dashboardService, cust
deleteItemsActionTitleFunc: deleteDashboardsActionTitle, deleteItemsActionTitleFunc: deleteDashboardsActionTitle,
deleteItemsContentFunc: deleteDashboardsText, deleteItemsContentFunc: deleteDashboardsText,
loadItemDetailsFunc: loadDashboard,
saveItemFunc: saveDashboard, saveItemFunc: saveDashboard,
clickItemFunc: openDashboard, clickItemFunc: openDashboard,
getItemTitleFunc: getDashboardTitle, getItemTitleFunc: getDashboardTitle,
itemCardTemplateUrl: dashboardCard, itemCardTemplateUrl: dashboardCard,
parentCtl: vm,
actionsList: dashboardActionsList, actionsList: dashboardActionsList,
groupActionsList: dashboardGroupActionsList, groupActionsList: dashboardGroupActionsList,
@ -128,7 +133,21 @@ export default function DashboardsController(userService, dashboardService, cust
}, },
name: function() { return $translate.instant('action.assign') }, name: function() { return $translate.instant('action.assign') },
details: function() { return $translate.instant('dashboard.assign-to-customer') }, details: function() { return $translate.instant('dashboard.assign-to-customer') },
icon: "assignment_ind" icon: "assignment_ind",
isEnabled: function(dashboard) {
return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid);
}
},
{
onAction: function ($event, item) {
unassignFromCustomer($event, item);
},
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.customerId && dashboard.customerId.id !== types.id.nullUid;
}
} }
); );
@ -200,6 +219,17 @@ export default function DashboardsController(userService, dashboardService, cust
}; };
if (vm.dashboardsScope === 'customer') { if (vm.dashboardsScope === 'customer') {
dashboardActionsList.push(
{
onAction: function ($event, item) {
exportDashboard($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('dashboard.export') },
icon: "file_download"
}
);
dashboardActionsList.push( dashboardActionsList.push(
{ {
onAction: function ($event, item) { onAction: function ($event, item) {
@ -272,6 +302,10 @@ export default function DashboardsController(userService, dashboardService, cust
return dashboard ? dashboard.title : ''; return dashboard ? dashboard.title : '';
} }
function loadDashboard(dashboard) {
return dashboardService.getDashboard(dashboard.id.id);
}
function saveDashboard(dashboard) { function saveDashboard(dashboard) {
return dashboardService.saveDashboard(dashboard); return dashboardService.saveDashboard(dashboard);
} }

2
ui/src/app/dashboard/index.js

@ -26,6 +26,7 @@ import thingsboardApiCustomer from '../api/customer.service';
import thingsboardDetailsSidenav from '../components/details-sidenav.directive'; import thingsboardDetailsSidenav from '../components/details-sidenav.directive';
import thingsboardDeviceFilter from '../components/device-filter.directive'; import thingsboardDeviceFilter from '../components/device-filter.directive';
import thingsboardWidgetConfig from '../components/widget-config.directive'; import thingsboardWidgetConfig from '../components/widget-config.directive';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
import thingsboardDashboard from '../components/dashboard.directive'; import thingsboardDashboard from '../components/dashboard.directive';
import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive'; import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive'; import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
@ -60,6 +61,7 @@ export default angular.module('thingsboard.dashboard', [
thingsboardDetailsSidenav, thingsboardDetailsSidenav,
thingsboardDeviceFilter, thingsboardDeviceFilter,
thingsboardWidgetConfig, thingsboardWidgetConfig,
thingsboardDashboardSelect,
thingsboardDashboard, thingsboardDashboard,
thingsboardExpandFullscreen, thingsboardExpandFullscreen,
thingsboardWidgetsBundleSelect thingsboardWidgetsBundleSelect

18
ui/src/app/device/attribute/add-widget-to-dashboard-dialog.controller.js

@ -19,7 +19,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
var vm = this; var vm = this;
vm.widget = widget; vm.widget = widget;
vm.dashboard = null; vm.dashboardId = null;
vm.addToDashboardType = 0; vm.addToDashboardType = 0;
vm.newDashboard = {}; vm.newDashboard = {};
vm.openDashboard = false; vm.openDashboard = false;
@ -33,12 +33,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
function add() { function add() {
$scope.theForm.$setPristine(); $scope.theForm.$setPristine();
var theDashboard;
if (vm.addToDashboardType === 0) { if (vm.addToDashboardType === 0) {
theDashboard = vm.dashboard; dashboardService.getDashboard(vm.dashboardId).then(
function success(dashboard) {
addWidgetToDashboard(dashboard);
},
function fail() {}
);
} else { } else {
theDashboard = vm.newDashboard; addWidgetToDashboard(vm.newDashboard);
} }
}
function addWidgetToDashboard(theDashboard) {
var aliasesInfo = { var aliasesInfo = {
datasourceAliases: {}, datasourceAliases: {},
targetDeviceAliases: {} targetDeviceAliases: {}
@ -51,7 +59,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
deviceList: [deviceId] deviceList: [deviceId]
} }
}; };
theDashboard = itembuffer.addWidgetToDashboard(theDashboard, widget, aliasesInfo, null, 48, -1, -1); theDashboard = itembuffer.addWidgetToDashboard(theDashboard, vm.widget, aliasesInfo, null, 48, -1, -1);
dashboardService.saveDashboard(theDashboard).then( dashboardService.saveDashboard(theDashboard).then(
function success(dashboard) { function success(dashboard) {
$mdDialog.hide(); $mdDialog.hide();

7
ui/src/app/device/attribute/add-widget-to-dashboard-dialog.tpl.html

@ -36,11 +36,12 @@
<md-radio-button flex ng-value=0 class="md-primary md-align-top-left md-radio-interactive"> <md-radio-button flex ng-value=0 class="md-primary md-align-top-left md-radio-interactive">
<section flex layout="column" style="width: 300px;"> <section flex layout="column" style="width: 300px;">
<span translate style="padding-bottom: 10px;">dashboard.select-existing</span> <span translate style="padding-bottom: 10px;">dashboard.select-existing</span>
<tb-dashboard-select the-form="theForm" <tb-dashboard-autocomplete the-form="theForm"
ng-disabled="loading || vm.addToDashboardType != 0"
tb-required="vm.addToDashboardType === 0" tb-required="vm.addToDashboardType === 0"
ng-model="vm.dashboard" ng-model="vm.dashboardId"
select-first-dashboard="false"> select-first-dashboard="false">
</tb-dashboard-select> </tb-dashboard-autocomplete>
</section> </section>
</md-radio-button> </md-radio-button>
<md-radio-button flex ng-value=1 class="md-primary md-align-top-left md-radio-interactive"> <md-radio-button flex ng-value=1 class="md-primary md-align-top-left md-radio-interactive">

4
ui/src/app/device/device-card.tpl.html

@ -15,4 +15,6 @@
limitations under the License. limitations under the License.
--> -->
<div></div> <div class="tb-small" ng-if="item &&
item.customerId.id != parentCtl.types.id.nullUid &&
parentCtl.devicesScope === 'tenant'" translate>device.assignedToCustomer</div>

1
ui/src/app/device/device.controller.js

@ -48,6 +48,7 @@ export default function DeviceController(userService, deviceService, customerSer
getItemTitleFunc: getDeviceTitle, getItemTitleFunc: getDeviceTitle,
itemCardTemplateUrl: deviceCard, itemCardTemplateUrl: deviceCard,
parentCtl: vm,
actionsList: deviceActionsList, actionsList: deviceActionsList,
groupActionsList: deviceGroupActionsList, groupActionsList: deviceGroupActionsList,

2
ui/src/app/device/index.js

@ -16,7 +16,6 @@
import uiRouter from 'angular-ui-router'; import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive'; import thingsboardGrid from '../components/grid.directive';
import thingsboardEvent from '../event'; import thingsboardEvent from '../event';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
import thingsboardApiUser from '../api/user.service'; import thingsboardApiUser from '../api/user.service';
import thingsboardApiDevice from '../api/device.service'; import thingsboardApiDevice from '../api/device.service';
import thingsboardApiCustomer from '../api/customer.service'; import thingsboardApiCustomer from '../api/customer.service';
@ -35,7 +34,6 @@ export default angular.module('thingsboard.device', [
uiRouter, uiRouter,
thingsboardGrid, thingsboardGrid,
thingsboardEvent, thingsboardEvent,
thingsboardDashboardSelect,
thingsboardApiUser, thingsboardApiUser,
thingsboardApiDevice, thingsboardApiDevice,
thingsboardApiCustomer thingsboardApiCustomer

298
ui/src/app/import-export/import-export.service.js

@ -24,18 +24,312 @@ import deviceAliasesTemplate from '../dashboard/device-aliases.tpl.html';
/* eslint-disable no-undef, angular/window-service, angular/document-service */ /* eslint-disable no-undef, angular/window-service, angular/document-service */
/*@ngInject*/ /*@ngInject*/
export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, deviceService, dashboardService, toast) { export default function ImportExport($log, $translate, $q, $mdDialog, $document, itembuffer, types,
deviceService, dashboardService, pluginService, ruleService, widgetService, toast) {
var service = { var service = {
exportDashboard: exportDashboard, exportDashboard: exportDashboard,
importDashboard: importDashboard, importDashboard: importDashboard,
exportWidget: exportWidget, exportWidget: exportWidget,
importWidget: importWidget importWidget: importWidget,
exportPlugin: exportPlugin,
importPlugin: importPlugin,
exportRule: exportRule,
importRule: importRule,
exportWidgetType: exportWidgetType,
importWidgetType: importWidgetType,
exportWidgetsBundle: exportWidgetsBundle,
importWidgetsBundle: importWidgetsBundle
} }
return service; return service;
// Widgets bundle functions
function exportWidgetsBundle(widgetsBundleId) {
widgetService.getWidgetsBundle(widgetsBundleId).then(
function success(widgetsBundle) {
var bundleAlias = widgetsBundle.alias;
var isSystem = widgetsBundle.tenantId.id === types.id.nullUid;
widgetService.getBundleWidgetTypes(bundleAlias, isSystem).then(
function success (widgetTypes) {
prepareExport(widgetsBundle);
var widgetsBundleItem = {
widgetsBundle: prepareExport(widgetsBundle),
widgetTypes: []
};
for (var t in widgetTypes) {
var widgetType = widgetTypes[t];
if (angular.isDefined(widgetType.bundleAlias)) {
delete widgetType.bundleAlias;
}
widgetsBundleItem.widgetTypes.push(prepareExport(widgetType));
}
var name = widgetsBundle.title;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(widgetsBundleItem, name + '.json');
},
function fail (rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('widgets-bundle.export-failed-error', {error: message}));
}
);
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('widgets-bundle.export-failed-error', {error: message}));
}
);
}
function importNextWidgetType(widgetTypes, bundleAlias, index, deferred) {
if (!widgetTypes || widgetTypes.length <= index) {
deferred.resolve();
} else {
var widgetType = widgetTypes[index];
widgetType.bundleAlias = bundleAlias;
widgetService.saveImportedWidgetType(widgetType).then(
function success() {
index++;
importNextWidgetType(widgetTypes, bundleAlias, index, deferred);
},
function fail() {
deferred.reject();
}
);
}
}
function importWidgetsBundle($event) {
var deferred = $q.defer();
openImportDialog($event, 'widgets-bundle.import', 'widgets-bundle.widgets-bundle-file').then(
function success(widgetsBundleItem) {
if (!validateImportedWidgetsBundle(widgetsBundleItem)) {
toast.showError($translate.instant('widgets-bundle.invalid-widgets-bundle-file-error'));
deferred.reject();
} else {
var widgetsBundle = widgetsBundleItem.widgetsBundle;
widgetService.saveWidgetsBundle(widgetsBundle).then(
function success(savedWidgetsBundle) {
var bundleAlias = savedWidgetsBundle.alias;
var widgetTypes = widgetsBundleItem.widgetTypes;
importNextWidgetType(widgetTypes, bundleAlias, 0, deferred);
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedWidgetsBundle(widgetsBundleItem) {
if (angular.isUndefined(widgetsBundleItem.widgetsBundle)) {
return false;
}
if (angular.isUndefined(widgetsBundleItem.widgetTypes)) {
return false;
}
var widgetsBundle = widgetsBundleItem.widgetsBundle;
if (angular.isUndefined(widgetsBundle.title)) {
return false;
}
var widgetTypes = widgetsBundleItem.widgetTypes;
for (var t in widgetTypes) {
var widgetType = widgetTypes[t];
if (!validateImportedWidgetType(widgetType)) {
return false;
}
}
return true;
}
// Widget type functions
function exportWidgetType(widgetTypeId) {
widgetService.getWidgetTypeById(widgetTypeId).then(
function success(widgetType) {
if (angular.isDefined(widgetType.bundleAlias)) {
delete widgetType.bundleAlias;
}
var name = widgetType.name;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(widgetType), name + '.json');
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('widget-type.export-failed-error', {error: message}));
}
);
}
function importWidgetType($event, bundleAlias) {
var deferred = $q.defer();
openImportDialog($event, 'widget-type.import', 'widget-type.widget-type-file').then(
function success(widgetType) {
if (!validateImportedWidgetType(widgetType)) {
toast.showError($translate.instant('widget-type.invalid-widget-type-file-error'));
deferred.reject();
} else {
widgetType.bundleAlias = bundleAlias;
widgetService.saveImportedWidgetType(widgetType).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedWidgetType(widgetType) {
if (angular.isUndefined(widgetType.name)
|| angular.isUndefined(widgetType.descriptor))
{
return false;
}
return true;
}
// Rule functions
function exportRule(ruleId) {
ruleService.getRule(ruleId).then(
function success(rule) {
var name = rule.name;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(rule), name + '.json');
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('rule.export-failed-error', {error: message}));
}
);
}
function importRule($event) {
var deferred = $q.defer();
openImportDialog($event, 'rule.import', 'rule.rule-file').then(
function success(rule) {
if (!validateImportedRule(rule)) {
toast.showError($translate.instant('rule.invalid-rule-file-error'));
deferred.reject();
} else {
rule.state = 'SUSPENDED';
ruleService.saveRule(rule).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedRule(rule) {
if (angular.isUndefined(rule.name)
|| angular.isUndefined(rule.pluginToken)
|| angular.isUndefined(rule.filters)
|| angular.isUndefined(rule.action))
{
return false;
}
return true;
}
// Plugin functions
function exportPlugin(pluginId) {
pluginService.getPlugin(pluginId).then(
function success(plugin) {
if (!plugin.configuration || plugin.configuration === null) {
plugin.configuration = {};
}
var name = plugin.name;
name = name.toLowerCase().replace(/\W/g,"_");
exportToPc(prepareExport(plugin), name + '.json');
},
function fail(rejection) {
var message = rejection;
if (!message) {
message = $translate.instant('error.unknown-error');
}
toast.showError($translate.instant('plugin.export-failed-error', {error: message}));
}
);
}
function importPlugin($event) {
var deferred = $q.defer();
openImportDialog($event, 'plugin.import', 'plugin.plugin-file').then(
function success(plugin) {
if (!validateImportedPlugin(plugin)) {
toast.showError($translate.instant('plugin.invalid-plugin-file-error'));
deferred.reject();
} else {
plugin.state = 'SUSPENDED';
pluginService.savePlugin(plugin).then(
function success() {
deferred.resolve();
},
function fail() {
deferred.reject();
}
);
}
},
function fail() {
deferred.reject();
}
);
return deferred.promise;
}
function validateImportedPlugin(plugin) {
if (angular.isUndefined(plugin.name)
|| angular.isUndefined(plugin.clazz)
|| angular.isUndefined(plugin.apiToken)
|| angular.isUndefined(plugin.configuration))
{
return false;
}
return true;
}
// Widget functions // Widget functions
function exportWidget(dashboard, widget) { function exportWidget(dashboard, widget) {

4
ui/src/app/layout/index.js

@ -27,6 +27,7 @@ import thingsboardApiUser from '../api/user.service';
import thingsboardNoAnimate from '../components/no-animate.directive'; import thingsboardNoAnimate from '../components/no-animate.directive';
import thingsboardSideMenu from '../components/side-menu.directive'; import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
import thingsboardUserMenu from './user-menu.directive'; import thingsboardUserMenu from './user-menu.directive';
@ -72,7 +73,8 @@ export default angular.module('thingsboard.home', [
thingsboardApiLogin, thingsboardApiLogin,
thingsboardApiUser, thingsboardApiUser,
thingsboardNoAnimate, thingsboardNoAnimate,
thingsboardSideMenu thingsboardSideMenu,
thingsboardDashboardAutocomplete
]) ])
.config(HomeRoutes) .config(HomeRoutes)
.controller('HomeController', HomeController) .controller('HomeController', HomeController)

37
ui/src/app/locale/locale.constant.js

@ -250,7 +250,7 @@ export default angular.module('thingsboard.locale', [])
"title-color": "Title color", "title-color": "Title color",
"import": "Import dashboard", "import": "Import dashboard",
"export": "Export dashboard", "export": "Export dashboard",
"export-failed-error": "Unable to export dashboard: {error}", "export-failed-error": "Unable to export dashboard: {{error}}",
"create-new-dashboard": "Create new dashboard", "create-new-dashboard": "Create new dashboard",
"dashboard-file": "Dashboard file", "dashboard-file": "Dashboard file",
"invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.", "invalid-dashboard-file-error": "Unable to import dashboard: Invalid dashboard data structure.",
@ -266,7 +266,8 @@ export default angular.module('thingsboard.locale', [])
"alias-resolution-error-title": "Dashboard aliases configuration error", "alias-resolution-error-title": "Dashboard aliases configuration error",
"invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.<br/>" + "invalid-aliases-config": "Unable to find any devices matching to some of the aliases filter.<br/>" +
"Please contact your administrator in order to resolve this issue.", "Please contact your administrator in order to resolve this issue.",
"select-devices": "Select devices" "select-devices": "Select devices",
"assignedToCustomer": "Assigned to customer"
}, },
"datakey": { "datakey": {
"settings": "Settings", "settings": "Settings",
@ -502,7 +503,13 @@ export default angular.module('thingsboard.locale', [])
"plugin-required": "Plugin is required.", "plugin-required": "Plugin is required.",
"plugin-require-match": "Please select an existing plugin.", "plugin-require-match": "Please select an existing plugin.",
"events": "Events", "events": "Events",
"details": "Details" "details": "Details",
"import": "Import plugin",
"export": "Export plugin",
"export-failed-error": "Unable to export plugin: {{error}}",
"create-new-plugin": "Create new plugin",
"plugin-file": "Plugin file",
"invalid-plugin-file-error": "Unable to import plugin: Invalid plugin data structure."
}, },
"position": { "position": {
"top": "Top", "top": "Top",
@ -559,7 +566,13 @@ export default angular.module('thingsboard.locale', [])
"create-action": "Create action", "create-action": "Create action",
"details": "Details", "details": "Details",
"events": "Events", "events": "Events",
"system": "System" "system": "System",
"import": "Import rule",
"export": "Export rule",
"export-failed-error": "Unable to export rule: {{error}}",
"create-new-rule": "Create new rule",
"rule-file": "Rule file",
"invalid-rule-file-error": "Unable to import rule: Invalid rule data structure."
}, },
"rule-plugin": { "rule-plugin": {
"management": "Rules and plugins management" "management": "Rules and plugins management"
@ -718,7 +731,13 @@ export default angular.module('thingsboard.locale', [])
"delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.", "delete-widgets-bundles-text": "Be careful, after the confirmation all selected widgets bundles will be removed and all related data will become unrecoverable.",
"no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.", "no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.",
"widgets-bundle-required": "Widgets bundle is required.", "widgets-bundle-required": "Widgets bundle is required.",
"system": "System" "system": "System",
"import": "Import widgets bundle",
"export": "Export widgets bundle",
"export-failed-error": "Unable to export widgets bundle: {{error}}",
"create-new-widgets-bundle": "Create new widgets bundle",
"widgets-bundle-file": "Widgets bundle file",
"invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure."
}, },
"widget-config": { "widget-config": {
"data": "Data", "data": "Data",
@ -747,6 +766,14 @@ export default angular.module('thingsboard.locale', [])
"remove-datasource": "Remove datasource", "remove-datasource": "Remove datasource",
"add-datasource": "Add datasource", "add-datasource": "Add datasource",
"target-device": "Target device" "target-device": "Target device"
},
"widget-type": {
"import": "Import widget type",
"export": "Export widget type",
"export-failed-error": "Unable to export widget type: {{error}}",
"create-new-widget-type": "Create new widget type",
"widget-type-file": "Widget type file",
"invalid-widget-type-file-error": "Unable to import widget type: Invalid widget type data structure."
} }
} }
} }

15
ui/src/app/plugin/plugin-fieldset.tpl.html

@ -15,9 +15,18 @@
limitations under the License. limitations under the License.
--> -->
<md-button ng-click="onActivatePlugin({event: $event})" ng-show="!isEdit && !isReadOnly && plugin.state === 'SUSPENDED'" class="md-raised md-primary">{{ 'plugin.activate' | translate }}</md-button> <md-button ng-click="onActivatePlugin({event: $event})"
<md-button ng-click="onSuspendPlugin({event: $event})" ng-show="!isEdit && !isReadOnly && plugin.state === 'ACTIVE'" class="md-raised md-primary">{{ 'plugin.suspend' | translate }}</md-button> ng-show="!isEdit && !isReadOnly && plugin.state === 'SUSPENDED'"
<md-button ng-click="onDeletePlugin({event: $event})" ng-show="!isEdit && !isReadOnly" class="md-raised md-primary">{{ 'plugin.delete' | translate }}</md-button> class="md-raised md-primary">{{ 'plugin.activate' | translate }}</md-button>
<md-button ng-click="onSuspendPlugin({event: $event})"
ng-show="!isEdit && !isReadOnly && plugin.state === 'ACTIVE'"
class="md-raised md-primary">{{ 'plugin.suspend' | translate }}</md-button>
<md-button ng-click="onExportPlugin({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'plugin.export' | translate }}</md-button>
<md-button ng-click="onDeletePlugin({event: $event})"
ng-show="!isEdit && !isReadOnly"
class="md-raised md-primary">{{ 'plugin.delete' | translate }}</md-button>
<md-content class="md-padding" layout="column" style="overflow-x: hidden"> <md-content class="md-padding" layout="column" style="overflow-x: hidden">
<fieldset ng-disabled="loading || !isEdit || isReadOnly"> <fieldset ng-disabled="loading || !isEdit || isReadOnly">

41
ui/src/app/plugin/plugin.controller.js

@ -21,9 +21,17 @@ import pluginCard from './plugin-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function PluginController(pluginService, userService, $state, $stateParams, $filter, $translate, types, helpLinks) { export default function PluginController(pluginService, userService, importExport, $state, $stateParams, $filter, $translate, types, helpLinks) {
var pluginActionsList = [ var pluginActionsList = [
{
onAction: function ($event, item) {
exportPlugin($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('plugin.export') },
icon: "file_download"
},
{ {
onAction: function ($event, item) { onAction: function ($event, item) {
activatePlugin($event, item); activatePlugin($event, item);
@ -57,6 +65,30 @@ export default function PluginController(pluginService, userService, $state, $st
} }
]; ];
var pluginAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('plugin.create-new-plugin') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importPlugin($event).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('plugin.import') },
icon: "file_upload"
}
];
var vm = this; var vm = this;
vm.types = types; vm.types = types;
@ -82,6 +114,7 @@ export default function PluginController(pluginService, userService, $state, $st
parentCtl: vm, parentCtl: vm,
actionsList: pluginActionsList, actionsList: pluginActionsList,
addItemActions: pluginAddItemActionsList,
onGridInited: gridInited, onGridInited: gridInited,
@ -107,6 +140,7 @@ export default function PluginController(pluginService, userService, $state, $st
vm.activatePlugin = activatePlugin; vm.activatePlugin = activatePlugin;
vm.suspendPlugin = suspendPlugin; vm.suspendPlugin = suspendPlugin;
vm.exportPlugin = exportPlugin;
function helpLinkIdForPlugin() { function helpLinkIdForPlugin() {
return helpLinks.getPluginLink(vm.grid.operatingItem()); return helpLinks.getPluginLink(vm.grid.operatingItem());
@ -160,6 +194,11 @@ export default function PluginController(pluginService, userService, $state, $st
} }
} }
function exportPlugin($event, plugin) {
$event.stopPropagation();
importExport.exportPlugin(plugin.id.id);
}
function activatePlugin(event, plugin) { function activatePlugin(event, plugin) {
pluginService.activatePlugin(plugin.id.id).then(function () { pluginService.activatePlugin(plugin.id.id).then(function () {
vm.grid.refreshList(); vm.grid.refreshList();

1
ui/src/app/plugin/plugin.directive.js

@ -83,6 +83,7 @@ export default function PluginDirective($compile, $templateCache, types, utils,
theForm: '=', theForm: '=',
onActivatePlugin: '&', onActivatePlugin: '&',
onSuspendPlugin: '&', onSuspendPlugin: '&',
onExportPlugin: '&',
onDeletePlugin: '&' onDeletePlugin: '&'
} }
}; };

1
ui/src/app/plugin/plugins.tpl.html

@ -28,6 +28,7 @@
the-form="vm.grid.detailsForm" the-form="vm.grid.detailsForm"
on-activate-plugin="vm.activatePlugin(event, vm.grid.detailsConfig.currentItem)" on-activate-plugin="vm.activatePlugin(event, vm.grid.detailsConfig.currentItem)"
on-suspend-plugin="vm.suspendPlugin(event, vm.grid.detailsConfig.currentItem)" on-suspend-plugin="vm.suspendPlugin(event, vm.grid.detailsConfig.currentItem)"
on-export-plugin="vm.exportPlugin(event, vm.grid.detailsConfig.currentItem)"
on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin> on-delete-plugin="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-plugin>
</md-tab> </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'plugin.events' | translate }}"> <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'plugin.events' | translate }}">

15
ui/src/app/rule/rule-fieldset.tpl.html

@ -15,9 +15,18 @@
limitations under the License. limitations under the License.
--> -->
<md-button ng-click="onActivateRule({event: $event})" ng-show="!isEdit && !isReadOnly && rule.state === 'SUSPENDED'" class="md-raised md-primary">{{ 'rule.activate' | translate }}</md-button> <md-button ng-click="onActivateRule({event: $event})"
<md-button ng-click="onSuspendRule({event: $event})" ng-show="!isEdit && !isReadOnly && rule.state === 'ACTIVE'" class="md-raised md-primary">{{ 'rule.suspend' | translate }}</md-button> ng-show="!isEdit && !isReadOnly && rule.state === 'SUSPENDED'"
<md-button ng-click="onDeleteRule({event: $event})" ng-show="!isEdit && !isReadOnly" class="md-raised md-primary">{{ 'rule.delete' | translate }}</md-button> class="md-raised md-primary">{{ 'rule.activate' | translate }}</md-button>
<md-button ng-click="onSuspendRule({event: $event})"
ng-show="!isEdit && !isReadOnly && rule.state === 'ACTIVE'"
class="md-raised md-primary">{{ 'rule.suspend' | translate }}</md-button>
<md-button ng-click="onExportRule({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'rule.export' | translate }}</md-button>
<md-button ng-click="onDeleteRule({event: $event})"
ng-show="!isEdit && !isReadOnly"
class="md-raised md-primary">{{ 'rule.delete' | translate }}</md-button>
<md-content class="md-padding tb-rule" layout="column"> <md-content class="md-padding tb-rule" layout="column">
<fieldset ng-disabled="loading || !isEdit || isReadOnly"> <fieldset ng-disabled="loading || !isEdit || isReadOnly">

40
ui/src/app/rule/rule.controller.js

@ -21,9 +21,17 @@ import ruleCard from './rule-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function RuleController(ruleService, userService, $state, $stateParams, $filter, $translate, types) { export default function RuleController(ruleService, userService, importExport, $state, $stateParams, $filter, $translate, types) {
var ruleActionsList = [ var ruleActionsList = [
{
onAction: function ($event, item) {
exportRule($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('rule.export') },
icon: "file_download"
},
{ {
onAction: function ($event, item) { onAction: function ($event, item) {
activateRule($event, item); activateRule($event, item);
@ -57,6 +65,29 @@ export default function RuleController(ruleService, userService, $state, $stateP
} }
]; ];
var ruleAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('rule.create-new-rule') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importRule($event).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('rule.import') },
icon: "file_upload"
}
];
var vm = this; var vm = this;
vm.types = types; vm.types = types;
@ -80,6 +111,7 @@ export default function RuleController(ruleService, userService, $state, $stateP
parentCtl: vm, parentCtl: vm,
actionsList: ruleActionsList, actionsList: ruleActionsList,
addItemActions: ruleAddItemActionsList,
onGridInited: gridInited, onGridInited: gridInited,
@ -104,6 +136,7 @@ export default function RuleController(ruleService, userService, $state, $stateP
vm.activateRule = activateRule; vm.activateRule = activateRule;
vm.suspendRule = suspendRule; vm.suspendRule = suspendRule;
vm.exportRule = exportRule;
function deleteRuleTitle(rule) { function deleteRuleTitle(rule) {
return $translate.instant('rule.delete-rule-title', {ruleName: rule.name}); return $translate.instant('rule.delete-rule-title', {ruleName: rule.name});
@ -153,6 +186,11 @@ export default function RuleController(ruleService, userService, $state, $stateP
} }
} }
function exportRule($event, rule) {
$event.stopPropagation();
importExport.exportRule(rule.id.id);
}
function activateRule(event, rule) { function activateRule(event, rule) {
ruleService.activateRule(rule.id.id).then(function () { ruleService.activateRule(rule.id.id).then(function () {
vm.grid.refreshList(); vm.grid.refreshList();

1
ui/src/app/rule/rule.directive.js

@ -177,6 +177,7 @@ export default function RuleDirective($compile, $templateCache, $mdDialog, $docu
theForm: '=', theForm: '=',
onActivateRule: '&', onActivateRule: '&',
onSuspendRule: '&', onSuspendRule: '&',
onExportRule: '&',
onDeleteRule: '&' onDeleteRule: '&'
} }
}; };

1
ui/src/app/rule/rules.tpl.html

@ -28,6 +28,7 @@
the-form="vm.grid.detailsForm" the-form="vm.grid.detailsForm"
on-activate-rule="vm.activateRule(event, vm.grid.detailsConfig.currentItem)" on-activate-rule="vm.activateRule(event, vm.grid.detailsConfig.currentItem)"
on-suspend-rule="vm.suspendRule(event, vm.grid.detailsConfig.currentItem)" on-suspend-rule="vm.suspendRule(event, vm.grid.detailsConfig.currentItem)"
on-export-rule="vm.exportRule(event, vm.grid.detailsConfig.currentItem)"
on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule> on-delete-rule="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-rule>
</md-tab> </md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'rule.events' | translate }}"> <md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode" label="{{ 'rule.events' | translate }}">

2
ui/src/app/user/user-fieldset.scss

@ -20,7 +20,7 @@
.tb-default-dashboard-label { .tb-default-dashboard-label {
padding-bottom: 8px; padding-bottom: 8px;
} }
tb-dashboard-select { tb-dashboard-autocomplete {
@media (min-width: $layout-breakpoint-gt-sm) { @media (min-width: $layout-breakpoint-gt-sm) {
padding-right: 12px; padding-right: 12px;
} }

9
ui/src/app/user/user-fieldset.tpl.html

@ -43,16 +43,17 @@
<label translate>user.description</label> <label translate>user.description</label>
<textarea ng-model="user.additionalInfo.description" rows="2"></textarea> <textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
</md-input-container> </md-input-container>
<section class="tb-default-dashboard" flex layout="column"ng-show="isCustomerUser()"> <section class="tb-default-dashboard" flex layout="column" ng-show="isCustomerUser()">
<span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span> <span class="tb-default-dashboard-label" ng-class="{'tb-disabled-label': loading || !isEdit}" translate>user.default-dashboard</span>
<section flex layout="column" layout-gt-sm="row"> <section flex layout="column" layout-gt-sm="row">
<tb-dashboard-select flex <tb-dashboard-autocomplete flex
ng-disabled="loading || !isEdit"
the-form="theForm" the-form="theForm"
ng-model="defaultDashboard" ng-model="user.additionalInfo.defaultDashboardId"
dashboards-scope="customer" dashboards-scope="customer"
customer-id="user.customerId.id" customer-id="user.customerId.id"
select-first-dashboard="false"> select-first-dashboard="false">
</tb-dashboard-select> </tb-dashboard-autocomplete>
<md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'user.always-fullscreen' | translate }}" <md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'user.always-fullscreen' | translate }}"
ng-model="user.additionalInfo.defaultDashboardFullscreen">{{ 'user.always-fullscreen' | translate }} ng-model="user.additionalInfo.defaultDashboardFullscreen">{{ 'user.always-fullscreen' | translate }}
</md-checkbox> </md-checkbox>

27
ui/src/app/user/user.directive.js

@ -23,7 +23,7 @@ import userFieldsetTemplate from './user-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function UserDirective($compile, $templateCache, dashboardService) { export default function UserDirective($compile, $templateCache/*, dashboardService*/) {
var linker = function (scope, element) { var linker = function (scope, element) {
var template = $templateCache.get(userFieldsetTemplate); var template = $templateCache.get(userFieldsetTemplate);
element.html(template); element.html(template);
@ -32,31 +32,6 @@ export default function UserDirective($compile, $templateCache, dashboardService
return scope.user && scope.user.authority === 'CUSTOMER_USER'; return scope.user && scope.user.authority === 'CUSTOMER_USER';
} }
scope.$watch('user', function(newUser, prevUser) {
if (!angular.equals(newUser, prevUser) && newUser) {
scope.defaultDashboard = null;
if (scope.isCustomerUser() && scope.user.additionalInfo &&
scope.user.additionalInfo.defaultDashboardId) {
dashboardService.getDashboard(scope.user.additionalInfo.defaultDashboardId).then(
function(dashboard) {
scope.defaultDashboard = dashboard;
}
)
}
}
});
scope.$watch('defaultDashboard', function(newDashboard, prevDashboard) {
if (!angular.equals(newDashboard, prevDashboard)) {
if (scope.isCustomerUser()) {
if (!scope.user.additionalInfo) {
scope.user.additionalInfo = {};
}
scope.user.additionalInfo.defaultDashboardId = newDashboard ? newDashboard.id.id : null;
}
}
});
$compile(element.contents())(scope); $compile(element.contents())(scope);
} }
return { return {

22
ui/src/app/widget/lib/google-map.js

@ -20,13 +20,14 @@ var gmGlobals = {
} }
export default class TbGoogleMap { export default class TbGoogleMap {
constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey) { constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey, gmDefaultMapType) {
var tbMap = this; var tbMap = this;
this.defaultZoomLevel = defaultZoomLevel; this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds; this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel; this.minZoomLevel = minZoomLevel;
this.tooltips = []; this.tooltips = [];
this.defaultMapType = gmDefaultMapType;
function clearGlobalId() { function clearGlobalId() {
if (gmGlobals.loadingGmId && gmGlobals.loadingGmId === tbMap.mapId) { if (gmGlobals.loadingGmId && gmGlobals.loadingGmId === tbMap.mapId) {
@ -44,6 +45,7 @@ export default class TbGoogleMap {
tbMap.map = new google.maps.Map($containerElement[0], { // eslint-disable-line no-undef tbMap.map = new google.maps.Map($containerElement[0], { // eslint-disable-line no-undef
scrollwheel: false, scrollwheel: false,
mapTypeId: getGoogleMapTypeId(tbMap.defaultMapType),
zoom: tbMap.defaultZoomLevel || 8 zoom: tbMap.defaultZoomLevel || 8
}); });
@ -53,6 +55,24 @@ export default class TbGoogleMap {
} }
/* eslint-disable no-undef */
function getGoogleMapTypeId(mapType) {
var mapTypeId = google.maps.MapTypeId.ROADMAP;
if (mapType) {
if (mapType === 'hybrid') {
mapTypeId = google.maps.MapTypeId.HYBRID;
} else if (mapType === 'satellite') {
mapTypeId = google.maps.MapTypeId.SATELLITE;
} else if (mapType === 'terrain') {
mapTypeId = google.maps.MapTypeId.TERRAIN;
}
}
return mapTypeId;
}
/* eslint-enable no-undef */
this.mapId = '' + Math.random().toString(36).substr(2, 9); this.mapId = '' + Math.random().toString(36).substr(2, 9);
this.apiKey = gmApiKey || ''; this.apiKey = gmApiKey || '';

2
ui/src/app/widget/lib/map-widget.js

@ -171,7 +171,7 @@ export default class TbMapWidget {
}; };
if (mapProvider === 'google-map') { if (mapProvider === 'google-map') {
this.map = new TbGoogleMap(ctx.$container, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey); this.map = new TbGoogleMap(ctx.$container, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel, settings.gmApiKey, settings.gmDefaultMapType);
} else if (mapProvider === 'openstreet-map') { } else if (mapProvider === 'openstreet-map') {
this.map = new TbOpenStreetMap(ctx.$container, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel); this.map = new TbOpenStreetMap(ctx.$container, initCallback, this.defaultZoomLevel, this.dontFitMapBounds, minZoomLevel);
} }

19
ui/src/app/widget/widget-library.controller.js

@ -20,7 +20,7 @@ import selectWidgetTypeTemplate from './select-widget-type.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function WidgetLibraryController($scope, $rootScope, $q, widgetService, userService, export default function WidgetLibraryController($scope, $rootScope, $q, widgetService, userService, importExport,
$state, $stateParams, $document, $mdDialog, $translate, $filter, types) { $state, $stateParams, $document, $mdDialog, $translate, $filter, types) {
var vm = this; var vm = this;
@ -36,6 +36,8 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
vm.dashboardInitFailed = dashboardInitFailed; vm.dashboardInitFailed = dashboardInitFailed;
vm.addWidgetType = addWidgetType; vm.addWidgetType = addWidgetType;
vm.openWidgetType = openWidgetType; vm.openWidgetType = openWidgetType;
vm.exportWidgetType = exportWidgetType;
vm.importWidgetType = importWidgetType;
vm.removeWidgetType = removeWidgetType; vm.removeWidgetType = removeWidgetType;
vm.loadWidgetLibrary = loadWidgetLibrary; vm.loadWidgetLibrary = loadWidgetLibrary;
vm.addWidgetType = addWidgetType; vm.addWidgetType = addWidgetType;
@ -173,6 +175,21 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
} }
} }
function exportWidgetType(event, widget) {
event.stopPropagation();
importExport.exportWidgetType(widget.id.id);
}
function importWidgetType($event) {
$event.stopPropagation();
importExport.importWidgetType($event, vm.widgetsBundle.alias).then(
function success() {
$state.go($state.current, $state.params, {reload: true});
},
function fail() {}
);
}
function removeWidgetType(event, widget) { function removeWidgetType(event, widget) {
var confirm = $mdDialog.confirm() var confirm = $mdDialog.confirm()
.targetEvent(event) .targetEvent(event)

41
ui/src/app/widget/widget-library.tpl.html

@ -31,20 +31,45 @@
widgets="vm.widgetTypes" widgets="vm.widgetTypes"
is-edit="false" is-edit="false"
is-edit-action-enabled="true" is-edit-action-enabled="true"
is-export-action-enabled="true"
is-remove-action-enabled="!vm.isReadOnly()" is-remove-action-enabled="!vm.isReadOnly()"
on-edit-widget="vm.openWidgetType(event, widget)" on-edit-widget="vm.openWidgetType(event, widget)"
on-export-widget="vm.exportWidgetType(event, widget)"
on-remove-widget="vm.removeWidgetType(event, widget)" on-remove-widget="vm.removeWidgetType(event, widget)"
load-widgets="vm.loadWidgetLibrary()" load-widgets="vm.loadWidgetLibrary()"
on-init="vm.dashboardInited(dashboard)" on-init="vm.dashboardInited(dashboard)"
on-init-failed="vm.dashboardInitFailed(e)"> on-init-failed="vm.dashboardInitFailed(e)">
</tb-dashboard> </tb-dashboard>
<section layout="row" layout-wrap class="tb-footer-buttons md-fab "> <section layout="row" layout-wrap class="tb-footer-buttons md-fab ">
<md-button ng-if="!vm.isReadOnly()" ng-disabled="loading" class="tb-btn-footer md-accent md-hue-2 md-fab" ng-click="vm.addWidgetType($event)" aria-label="{{ 'widget.add-widget-type' | translate }}" > <md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isReadOnly()"
<md-tooltip md-direction="top"> md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
{{ 'widget.add-widget-type' | translate }} <md-fab-trigger>
</md-tooltip> <md-button ng-disabled="loading"
<md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons"> class="tb-btn-footer md-accent md-hue-2 md-fab"
add aria-label="{{ 'widget.add-widget-type' | translate }}">
</md-icon> <md-tooltip md-direction="top">
</md-button> {{ 'widget.add-widget-type' | translate }}
</md-tooltip>
<ng-md-icon icon="add"></ng-md-icon>
</md-button>
</md-fab-trigger>
<md-fab-actions>
<md-button ng-disabled="loading"
class="tmd-accent md-hue-2 md-fab" ng-click="vm.addWidgetType($event)"
aria-label="{{ 'action.create' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget-type.create-new-widget-type' | translate }}
</md-tooltip>
<ng-md-icon icon="insert_drive_file"></ng-md-icon>
</md-button>
<md-button ng-disabled="loading"
class="tmd-accent md-hue-2 md-fab" ng-click="vm.importWidgetType($event)"
aria-label="{{ 'action.import' | translate }}">
<md-tooltip md-direction="top">
{{ 'widget-type.import' | translate }}
</md-tooltip>
<ng-md-icon icon="file_upload"></ng-md-icon>
</md-button>
</md-fab-actions>
</md-fab-speed-dial>
</section> </section>

7
ui/src/app/widget/widgets-bundle-fieldset.tpl.html

@ -15,7 +15,12 @@
limitations under the License. limitations under the License.
--> -->
<md-button ng-click="onDeleteWidgetsBundle({event: $event})" ng-show="!isEdit && !isReadOnly" class="md-raised md-primary">{{ 'widgets-bundle.delete' | translate }}</md-button> <md-button ng-click="onExportWidgetsBundle({event: $event})"
ng-show="!isEdit"
class="md-raised md-primary">{{ 'widgets-bundle.export' | translate }}</md-button>
<md-button ng-click="onDeleteWidgetsBundle({event: $event})"
ng-show="!isEdit && !isReadOnly"
class="md-raised md-primary">{{ 'widgets-bundle.delete' | translate }}</md-button>
<md-content class="md-padding" layout="column"> <md-content class="md-padding" layout="column">
<fieldset ng-disabled="loading || !isEdit"> <fieldset ng-disabled="loading || !isEdit">

41
ui/src/app/widget/widgets-bundle.controller.js

@ -21,9 +21,17 @@ import widgetsBundleCard from './widgets-bundle-card.tpl.html';
/* eslint-enable import/no-unresolved, import/default */ /* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/ /*@ngInject*/
export default function WidgetsBundleController(widgetService, userService, $state, $stateParams, $filter, $translate, types) { export default function WidgetsBundleController(widgetService, userService, importExport, $state, $stateParams, $filter, $translate, types) {
var widgetsBundleActionsList = [ var widgetsBundleActionsList = [
{
onAction: function ($event, item) {
exportWidgetsBundle($event, item);
},
name: function() { $translate.instant('action.export') },
details: function() { return $translate.instant('widgets-bundle.export') },
icon: "file_download"
},
{ {
onAction: function ($event, item) { onAction: function ($event, item) {
vm.grid.openItem($event, item); vm.grid.openItem($event, item);
@ -43,6 +51,29 @@ export default function WidgetsBundleController(widgetService, userService, $sta
} }
]; ];
var widgetsBundleAddItemActionsList = [
{
onAction: function ($event) {
vm.grid.addItem($event);
},
name: function() { return $translate.instant('action.create') },
details: function() { return $translate.instant('widgets-bundle.create-new-widgets-bundle') },
icon: "insert_drive_file"
},
{
onAction: function ($event) {
importExport.importWidgetsBundle($event).then(
function() {
vm.grid.refreshList();
}
);
},
name: function() { return $translate.instant('action.import') },
details: function() { return $translate.instant('widgets-bundle.import') },
icon: "file_upload"
}
];
var vm = this; var vm = this;
vm.types = types; vm.types = types;
@ -67,6 +98,7 @@ export default function WidgetsBundleController(widgetService, userService, $sta
parentCtl: vm, parentCtl: vm,
actionsList: widgetsBundleActionsList, actionsList: widgetsBundleActionsList,
addItemActions: widgetsBundleAddItemActionsList,
onGridInited: gridInited, onGridInited: gridInited,
@ -90,6 +122,8 @@ export default function WidgetsBundleController(widgetService, userService, $sta
vm.widgetsBundleGridConfig.topIndex = $stateParams.topIndex; vm.widgetsBundleGridConfig.topIndex = $stateParams.topIndex;
} }
vm.exportWidgetsBundle = exportWidgetsBundle;
function deleteWidgetsBundleTitle(widgetsBundle) { function deleteWidgetsBundleTitle(widgetsBundle) {
return $translate.instant('widgets-bundle.delete-widgets-bundle-title', {widgetsBundleTitle: widgetsBundle.title}); return $translate.instant('widgets-bundle.delete-widgets-bundle-title', {widgetsBundleTitle: widgetsBundle.title});
} }
@ -138,6 +172,11 @@ export default function WidgetsBundleController(widgetService, userService, $sta
} }
} }
function exportWidgetsBundle($event, widgetsBundle) {
$event.stopPropagation();
importExport.exportWidgetsBundle(widgetsBundle.id.id);
}
function openWidgetsBundle($event, widgetsBundle) { function openWidgetsBundle($event, widgetsBundle) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();

1
ui/src/app/widget/widgets-bundle.directive.js

@ -34,6 +34,7 @@ export default function WidgetsBundleDirective($compile, $templateCache) {
isEdit: '=', isEdit: '=',
isReadOnly: '=', isReadOnly: '=',
theForm: '=', theForm: '=',
onExportWidgetsBundle: '&',
onDeleteWidgetsBundle: '&' onDeleteWidgetsBundle: '&'
} }
}; };

4
ui/src/app/widget/widgets-bundles.tpl.html

@ -20,5 +20,7 @@
is-edit="vm.grid.detailsConfig.isDetailsEditMode" is-edit="vm.grid.detailsConfig.isDetailsEditMode"
is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())" is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
the-form="vm.grid.detailsForm" the-form="vm.grid.detailsForm"
on-delete-widgets-bundle="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-widgets-bundle> on-export-widgets-bundle="vm.exportWidgetsBundle(event, vm.grid.detailsConfig.currentItem)"
on-delete-widgets-bundle="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)">
</tb-widgets-bundle>
</tb-grid> </tb-grid>

7
ui/src/scss/main.scss

@ -229,6 +229,13 @@ label {
} }
} }
div {
&.tb-small {
color: rgba(0,0,0,0.54);
font-size: 14px;
}
}
/*********************** /***********************
* Flow * Flow
***********************/ ***********************/

Loading…
Cancel
Save