Browse Source

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

pull/78/head
Igor Kulikov 9 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 {
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;
}

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.web.bind.annotation.*;
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;
@ -118,7 +119,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
public TextPageData<Dashboard> getTenantDashboards(
public TextPageData<DashboardInfo> getTenantDashboards(
@RequestParam int limit,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@ -135,7 +136,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET)
@ResponseBody
public TextPageData<Dashboard> getCustomerDashboards(
public TextPageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
@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 org.apache.commons.lang3.RandomStringUtils;
import org.thingsboard.server.common.data.Customer;
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.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
@ -43,7 +40,7 @@ import com.fasterxml.jackson.core.type.TypeReference;
public class DashboardControllerTest extends AbstractControllerTest {
private IdComparator<Dashboard> idComparator = new IdComparator<>();
private IdComparator<DashboardInfo> idComparator = new IdComparator<>();
private Tenant savedTenant;
private User tenantAdmin;
@ -203,18 +200,18 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test
public void testFindTenantDashboards() throws Exception {
List<Dashboard> dashboards = new ArrayList<>();
List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<173;i++) {
Dashboard dashboard = new Dashboard();
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);
TextPageData<Dashboard> pageData = null;
TextPageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -230,32 +227,32 @@ public class DashboardControllerTest extends AbstractControllerTest {
@Test
public void testFindTenantDashboardsByTitle() throws Exception {
String title1 = "Dashboard title 1";
List<Dashboard> dashboardsTitle1 = new ArrayList<>();
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<134;i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title1+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
dashboardsTitle1.add(doPost("/api/dashboard", dashboard, Dashboard.class));
dashboardsTitle1.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class)));
}
String title2 = "Dashboard title 2";
List<Dashboard> dashboardsTitle2 = new ArrayList<>();
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<112;i++) {
Dashboard dashboard = new Dashboard();
String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));
String title = title2+suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
dashboard.setTitle(title);
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);
TextPageData<Dashboard> pageData = null;
TextPageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -267,11 +264,11 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>();
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2);
do {
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -283,25 +280,25 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (Dashboard dashboard : loadedDashboardsTitle1) {
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/dashboard/"+dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(4, title1);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (Dashboard dashboard : loadedDashboardsTitle2) {
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/dashboard/"+dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(4, title2);
pageData = doGetTypedWithPageLink("/api/tenant/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
@ -313,21 +310,21 @@ public class DashboardControllerTest extends AbstractControllerTest {
customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId();
List<Dashboard> dashboards = new ArrayList<>();
List<DashboardInfo> dashboards = new ArrayList<>();
for (int i=0;i<173;i++) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Dashboard"+i);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboards.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class));
dashboards.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
List<Dashboard> loadedDashboards = new ArrayList<>();
List<DashboardInfo> loadedDashboards = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(21);
TextPageData<Dashboard> pageData = null;
TextPageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboards.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -348,7 +345,7 @@ public class DashboardControllerTest extends AbstractControllerTest {
CustomerId customerId = customer.getId();
String title1 = "Dashboard title 1";
List<Dashboard> dashboardsTitle1 = new ArrayList<>();
List<DashboardInfo> dashboardsTitle1 = new ArrayList<>();
for (int i=0;i<125;i++) {
Dashboard dashboard = new Dashboard();
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();
dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class));
dashboardsTitle1.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
String title2 = "Dashboard title 2";
List<Dashboard> dashboardsTitle2 = new ArrayList<>();
List<DashboardInfo> dashboardsTitle2 = new ArrayList<>();
for (int i=0;i<143;i++) {
Dashboard dashboard = new Dashboard();
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();
dashboard.setTitle(title);
dashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
dashboardsTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class));
dashboardsTitle2.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString()
+ "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class)));
}
List<Dashboard> loadedDashboardsTitle1 = new ArrayList<>();
List<DashboardInfo> loadedDashboardsTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(18, title1);
TextPageData<Dashboard> pageData = null;
TextPageData<DashboardInfo> pageData = null;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -389,11 +386,11 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1);
List<Dashboard> loadedDashboardsTitle2 = new ArrayList<>();
List<DashboardInfo> loadedDashboardsTitle2 = new ArrayList<>();
pageLink = new TextPageLink(7, title2);
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
loadedDashboardsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
@ -405,25 +402,25 @@ public class DashboardControllerTest extends AbstractControllerTest {
Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2);
for (Dashboard dashboard : loadedDashboardsTitle1) {
for (DashboardInfo dashboard : loadedDashboardsTitle1) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(5, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
for (Dashboard dashboard : loadedDashboardsTitle2) {
for (DashboardInfo dashboard : loadedDashboardsTitle2) {
doDelete("/api/customer/dashboard/" + dashboard.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new TextPageLink(9, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?",
new TypeReference<TextPageData<Dashboard>>(){}, pageLink);
new TypeReference<TextPageData<DashboardInfo>>(){}, pageLink);
Assert.assertFalse(pageData.hasNext());
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;
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 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 TenantId tenantId;
private CustomerId customerId;
private String title;
private JsonNode configuration;
public Dashboard() {
@ -37,37 +31,14 @@ public class Dashboard extends SearchTextBased<DashboardId> {
public Dashboard(DashboardId 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() {
return customerId;
public Dashboard(DashboardInfo dashboardInfo) {
super(dashboardInfo);
}
public void setCustomerId(CustomerId customerId) {
this.customerId = customerId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
public Dashboard(Dashboard dashboard) {
super(dashboard);
this.configuration = dashboard.getConfiguration();
}
public JsonNode getConfiguration() {
@ -78,19 +49,11 @@ public class Dashboard extends SearchTextBased<DashboardId> {
this.configuration = configuration;
}
@Override
public String getSearchText() {
return title;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.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;
}
@ -108,21 +71,6 @@ public class Dashboard extends SearchTextBased<DashboardId> {
return false;
} else if (!configuration.equals(other.configuration))
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;
}
@ -130,15 +78,14 @@ public class Dashboard extends SearchTextBased<DashboardId> {
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Dashboard [tenantId=");
builder.append(tenantId);
builder.append(getTenantId());
builder.append(", customerId=");
builder.append(customerId);
builder.append(getCustomerId());
builder.append(", title=");
builder.append(title);
builder.append(getTitle());
builder.append(", configuration=");
builder.append(configuration);
builder.append("]");
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.dao.Dao;
import org.thingsboard.server.dao.model.DashboardEntity;
import org.thingsboard.server.dao.model.DashboardInfoEntity;
/**
* The Interface DashboardDao.
@ -38,23 +39,4 @@ public interface DashboardDao extends Dao<DashboardEntity> {
*/
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.server.dao.model.DashboardInfoEntity;
@Component
@Slf4j
@ -56,28 +57,4 @@ public class DashboardDaoImpl extends AbstractSearchTextDao<DashboardEntity> imp
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;
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;
@ -34,11 +35,11 @@ public interface DashboardService {
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 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);

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 org.apache.commons.lang3.StringUtils;
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;
@ -30,9 +31,7 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.DashboardEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
@ -40,7 +39,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.Validator;
@Service
@ -49,7 +47,10 @@ public class DashboardServiceImpl implements DashboardService {
@Autowired
private DashboardDao dashboardDao;
@Autowired
private DashboardInfoDao dashboardInfoDao;
@Autowired
private TenantDao tenantDao;
@ -94,13 +95,13 @@ public class DashboardServiceImpl implements DashboardService {
}
@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);
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardEntity> dashboardEntities = dashboardDao.findDashboardsByTenantId(tenantId.getId(), pageLink);
List<Dashboard> dashboards = convertDataList(dashboardEntities);
return new TextPageData<Dashboard>(dashboards, pageLink);
List<DashboardInfoEntity> dashboardEntities = dashboardInfoDao.findDashboardsByTenantId(tenantId.getId(), pageLink);
List<DashboardInfo> dashboards = convertDataList(dashboardEntities);
return new TextPageData<DashboardInfo>(dashboards, pageLink);
}
@Override
@ -111,14 +112,14 @@ public class DashboardServiceImpl implements DashboardService {
}
@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);
Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
Validator.validateId(customerId, "Incorrect customerId " + customerId);
Validator.validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DashboardEntity> dashboardEntities = dashboardDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
List<Dashboard> dashboards = convertDataList(dashboardEntities);
return new TextPageData<Dashboard>(dashboards, pageLink);
List<DashboardInfoEntity> dashboardEntities = dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
List<DashboardInfo> dashboards = convertDataList(dashboardEntities);
return new TextPageData<DashboardInfo>(dashboards, pageLink);
}
@Override
@ -158,21 +159,21 @@ public class DashboardServiceImpl implements DashboardService {
}
};
private PaginatedRemover<TenantId, DashboardEntity> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardEntity>() {
private PaginatedRemover<TenantId, DashboardInfoEntity> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardInfoEntity>() {
@Override
protected List<DashboardEntity> findEntities(TenantId id, TextPageLink pageLink) {
return dashboardDao.findDashboardsByTenantId(id.getId(), pageLink);
protected List<DashboardInfoEntity> findEntities(TenantId id, TextPageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink);
}
@Override
protected void removeEntity(DashboardEntity entity) {
protected void removeEntity(DashboardInfoEntity entity) {
deleteDashboard(new DashboardId(entity.getId()));
}
};
class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardEntity> {
class CustomerDashboardsUnassigner extends PaginatedRemover<CustomerId, DashboardInfoEntity> {
private TenantId tenantId;
@ -181,12 +182,12 @@ public class DashboardServiceImpl implements DashboardService {
}
@Override
protected List<DashboardEntity> findEntities(CustomerId id, TextPageLink pageLink) {
return dashboardDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
protected List<DashboardInfoEntity> findEntities(CustomerId id, TextPageLink pageLink) {
return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(tenantId.getId(), id.getId(), pageLink);
}
@Override
protected void removeEntity(DashboardEntity entity) {
protected void removeEntity(DashboardInfoEntity entity) {
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,
configuration text,
PRIMARY KEY (id, tenant_id, customer_id)
);
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_tenant_and_search_text AS
SELECT *
from thingsboard.dashboard
WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( tenant_id, customer_id, search_text, id )
WITH CLUSTERING ORDER BY ( customer_id DESC, search_text ASC, id DESC );
PRIMARY KEY ( tenant_id, search_text, id, customer_id )
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC );
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.dashboard_by_customer_and_search_text AS
SELECT *

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

2
ui/package.json

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

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

@ -22,9 +22,10 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
.name;
/*@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,
currentUserDetails = null,
allowedDashboardIds = [],
userLoaded = false;
var refreshTokenQueue = [];
@ -90,6 +91,7 @@ function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelpe
function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) {
currentUser = null;
currentUserDetails = null;
allowedDashboardIds = [];
if (!jwtToken) {
clearTokenData();
if (notify) {
@ -230,13 +232,28 @@ function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelpe
getUser(currentUser.userId).then(
function success(user) {
currentUserDetails = user;
$rootScope.forceFullscreen = false;
if (currentUserDetails.additionalInfo &&
currentUserDetails.additionalInfo.defaultDashboardFullscreen) {
$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 {
$rootScope.forceFullscreen = false;
deferred.resolve();
}
deferred.resolve();
},
function fail() {
deferred.reject();
@ -362,7 +379,9 @@ function UserService($http, $q, $rootScope, adminService, toast, store, jwtHelpe
if ($rootScope.forceFullscreen) {
if (to.name === 'home.profile') {
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;
}
}

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

@ -92,6 +92,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
getInstantWidgetInfo: getInstantWidgetInfo,
deleteWidgetType: deleteWidgetType,
saveWidgetType: saveWidgetType,
saveImportedWidgetType: saveImportedWidgetType,
getWidgetType: getWidgetType,
getWidgetTypeById: getWidgetTypeById,
toWidgetInfo: toWidgetInfo
@ -683,6 +684,19 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
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) {
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;
/*@ngInject*/
function DashboardSelect($compile, $templateCache, $q, dashboardService, userService) {
function DashboardSelect($compile, $templateCache, $q, types, dashboardService, userService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(dashboardSelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.dashboard = null;
scope.dashboardSearchText = '';
scope.dashboardId = null;
scope.fetchDashboards = function(searchText) {
var pageLink = {limit: 10, textSearch: searchText};
var pageLink = {limit: 100};
var deferred = $q.defer();
var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
var promise;
if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') {
if (scope.customerId && scope.customerId != types.id.nullUid) {
promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink);
} else {
promise = dashboardService.getTenantDashboards(pageLink);
promise = $q.when({data: []});
}
promise.then(function success(result) {
deferred.resolve(result.data);
}, function fail() {
deferred.reject();
});
return deferred.promise;
} else {
promise = dashboardService.getTenantDashboards(pageLink);
}
scope.dashboardSearchTextChanged = function() {
}
promise.then(function success(result) {
scope.dashboards = result.data;
}, function fail() {
scope.dashboards = [];
});
scope.updateView = function () {
ngModelCtrl.$setViewValue(scope.dashboard);
ngModelCtrl.$setViewValue(scope.dashboardId);
}
ngModelCtrl.$render = function () {
if (ngModelCtrl.$viewValue) {
scope.dashboard = ngModelCtrl.$viewValue;
scope.dashboardId = ngModelCtrl.$viewValue;
} else {
scope.dashboard = null;
scope.dashboardId = null;
}
}
scope.$watch('dashboard', function () {
scope.$watch('dashboardId', 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);
}
@ -101,9 +84,8 @@ function DashboardSelect($compile, $templateCache, $q, dashboardService, userSer
scope: {
dashboardsScope: '@',
customerId: '=',
theForm: '=?',
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
* 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;
tb-dashboard-select {
md-select {
pointer-events: all;
}
}

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

@ -15,28 +15,12 @@
limitations under the License.
-->
<md-autocomplete ng-required="tbRequired"
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>
<md-select ng-required="tbRequired"
ng-disabled="disabled"
ng-model="dashboardId"
aria-label="{{ 'dashboard.dashboard' | translate }}">
<md-option ng-repeat="dashboard in dashboards" ng-value="dashboard.id.id">
{{dashboard.title}}
</md-option>
</md-select>

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

@ -269,6 +269,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
return $q.when([]);
};
vm.loadItemDetailsFunc = vm.config.loadItemDetailsFunc || function (item) {
return $q.when(item);
};
vm.saveItemFunc = vm.config.saveItemFunc || function (item) {
return $q.when(item);
};
@ -423,9 +427,12 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
return;
}
}
vm.detailsConfig.currentItem = item;
vm.detailsConfig.isDetailsEditMode = false;
vm.detailsConfig.isDetailsOpen = true;
vm.loadItemDetailsFunc(item).then(function success(detailsItem) {
detailsItem.index = item.index;
vm.detailsConfig.currentItem = detailsItem;
vm.detailsConfig.isDetailsEditMode = false;
vm.detailsConfig.isDetailsOpen = true;
});
}
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 originalTimewindow = null;
var subscriptionTimewindow = null;
var dataUpdateCaf = null;
var cafs = {};
var varsRegex = /\$\{([^\}]*)\}/g;
@ -186,11 +186,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function onDataUpdated() {
if (widgetContext.inited) {
if (dataUpdateCaf) {
dataUpdateCaf();
dataUpdateCaf = null;
if (cafs['dataUpdate']) {
cafs['dataUpdate']();
cafs['dataUpdate'] = null;
}
dataUpdateCaf = tbRaf(function() {
cafs['dataUpdate'] = tbRaf(function() {
try {
widgetTypeInstance.onDataUpdated();
} catch (e) {
@ -222,7 +222,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
function onResize() {
if (checkSize()) {
if (widgetContext.inited) {
tbRaf(function() {
if (cafs['resize']) {
cafs['resize']();
cafs['resize'] = null;
}
cafs['resize'] = tbRaf(function() {
try {
widgetTypeInstance.onResize();
} catch (e) {
@ -251,7 +255,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (widgetContext.isEdit != isEdit) {
widgetContext.isEdit = isEdit;
if (widgetContext.inited) {
tbRaf(function() {
if (cafs['editMode']) {
cafs['editMode']();
cafs['editMode'] = null;
}
cafs['editMode'] = tbRaf(function() {
try {
widgetTypeInstance.onEditModeChanged();
} catch (e) {
@ -266,7 +274,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (widgetContext.isMobile != isMobile) {
widgetContext.isMobile = isMobile;
if (widgetContext.inited) {
tbRaf(function() {
if (cafs['mobileMode']) {
cafs['mobileMode']();
cafs['mobileMode'] = null;
}
cafs['mobileMode'] = tbRaf(function() {
try {
widgetTypeInstance.onMobileModeChanged();
} catch (e) {
@ -282,6 +294,12 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
if (widgetContext.inited) {
widgetContext.inited = false;
widgetContext.dataUpdatePending = false;
for (var cafId in cafs) {
if (cafs[cafId]) {
cafs[cafId]();
cafs[cafId] = null;
}
}
try {
widgetTypeInstance.onDestroy();
} catch (e) {

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

@ -15,4 +15,6 @@
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.
-->
<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="onUnassignFromCustomer({event: $event})" ng-show="!isEdit && dashboardScope === 'customer'" 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-button ng-click="onAssignToCustomer({event: $event})"
ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer"
class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button>
<md-button ng-click="onUnassignFromCustomer({event: $event})"
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-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">
<md-input-container class="md-block">
<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,
$scope, $state, $stateParams, $mdDialog, $timeout, $document, $q, $translate, $filter) {
var user = userService.getCurrentUser();
var vm = this;
vm.user = userService.getCurrentUser();
vm.dashboard = null;
vm.editingWidget = null;
vm.editingWidgetOriginal = null;
@ -49,6 +48,15 @@ export default function DashboardController(types, widgetService, userService,
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', {
get: function() { return vm.isToolbarOpened || vm.isEdit; },
set: function() { }
@ -75,6 +83,7 @@ export default function DashboardController(types, widgetService, userService,
vm.prepareDashboardContextMenu = prepareDashboardContextMenu;
vm.prepareWidgetContextMenu = prepareWidgetContextMenu;
vm.editWidget = editWidget;
vm.exportDashboard = exportDashboard;
vm.exportWidget = exportWidget;
vm.importWidget = importWidget;
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() {
vm.latestWidgetTypes = [];
vm.timeseriesWidgetTypes = [];
@ -251,11 +274,11 @@ export default function DashboardController(types, widgetService, userService,
}
function isTenantAdmin() {
return user.authority === 'TENANT_ADMIN';
return vm.user.authority === 'TENANT_ADMIN';
}
function isSystemAdmin() {
return user.authority === 'SYS_ADMIN';
return vm.user.authority === 'SYS_ADMIN';
}
function noData() {
@ -267,7 +290,7 @@ export default function DashboardController(types, widgetService, userService,
}
function showDashboardToolbar() {
return vm.dashboardInitComplete && !vm.configurationError;
return vm.dashboardInitComplete;
}
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) {
$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 */
/*@ngInject*/
export default function DashboardDirective($compile, $templateCache) {
export default function DashboardDirective($compile, $templateCache, types, customerService) {
var linker = function (scope, element) {
var template = $templateCache.get(dashboardFieldsetTemplate);
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);
}
return {

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

@ -49,6 +49,13 @@
</md-button>
<tb-user-menu ng-show="forceFullscreen" display-user-info="true">
</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>
<tb-aliases-device-select ng-show="!vm.isEdit"
@ -70,6 +77,11 @@
</md-tooltip>
<md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
</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-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 */
/*@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;
@ -41,6 +42,7 @@ export default function DashboardsController(userService, dashboardService, cust
var dashboardGroupActionsList = [];
var vm = this;
vm.types = types;
vm.dashboardGridConfig = {
deleteItemTitleFunc: deleteDashboardTitle,
@ -49,12 +51,15 @@ export default function DashboardsController(userService, dashboardService, cust
deleteItemsActionTitleFunc: deleteDashboardsActionTitle,
deleteItemsContentFunc: deleteDashboardsText,
loadItemDetailsFunc: loadDashboard,
saveItemFunc: saveDashboard,
clickItemFunc: openDashboard,
getItemTitleFunc: getDashboardTitle,
itemCardTemplateUrl: dashboardCard,
parentCtl: vm,
actionsList: dashboardActionsList,
groupActionsList: dashboardGroupActionsList,
@ -128,7 +133,21 @@ export default function DashboardsController(userService, dashboardService, cust
},
name: function() { return $translate.instant('action.assign') },
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') {
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(
{
onAction: function ($event, item) {
@ -272,6 +302,10 @@ export default function DashboardsController(userService, dashboardService, cust
return dashboard ? dashboard.title : '';
}
function loadDashboard(dashboard) {
return dashboardService.getDashboard(dashboard.id.id);
}
function 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 thingsboardDeviceFilter from '../components/device-filter.directive';
import thingsboardWidgetConfig from '../components/widget-config.directive';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
import thingsboardDashboard from '../components/dashboard.directive';
import thingsboardExpandFullscreen from '../components/expand-fullscreen.directive';
import thingsboardWidgetsBundleSelect from '../components/widgets-bundle-select.directive';
@ -60,6 +61,7 @@ export default angular.module('thingsboard.dashboard', [
thingsboardDetailsSidenav,
thingsboardDeviceFilter,
thingsboardWidgetConfig,
thingsboardDashboardSelect,
thingsboardDashboard,
thingsboardExpandFullscreen,
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;
vm.widget = widget;
vm.dashboard = null;
vm.dashboardId = null;
vm.addToDashboardType = 0;
vm.newDashboard = {};
vm.openDashboard = false;
@ -33,12 +33,20 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
function add() {
$scope.theForm.$setPristine();
var theDashboard;
if (vm.addToDashboardType === 0) {
theDashboard = vm.dashboard;
dashboardService.getDashboard(vm.dashboardId).then(
function success(dashboard) {
addWidgetToDashboard(dashboard);
},
function fail() {}
);
} else {
theDashboard = vm.newDashboard;
addWidgetToDashboard(vm.newDashboard);
}
}
function addWidgetToDashboard(theDashboard) {
var aliasesInfo = {
datasourceAliases: {},
targetDeviceAliases: {}
@ -51,7 +59,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
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(
function success(dashboard) {
$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">
<section flex layout="column" style="width: 300px;">
<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"
ng-model="vm.dashboard"
ng-model="vm.dashboardId"
select-first-dashboard="false">
</tb-dashboard-select>
</tb-dashboard-autocomplete>
</section>
</md-radio-button>
<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.
-->
<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,
itemCardTemplateUrl: deviceCard,
parentCtl: vm,
actionsList: deviceActionsList,
groupActionsList: deviceGroupActionsList,

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

@ -16,7 +16,6 @@
import uiRouter from 'angular-ui-router';
import thingsboardGrid from '../components/grid.directive';
import thingsboardEvent from '../event';
import thingsboardDashboardSelect from '../components/dashboard-select.directive';
import thingsboardApiUser from '../api/user.service';
import thingsboardApiDevice from '../api/device.service';
import thingsboardApiCustomer from '../api/customer.service';
@ -35,7 +34,6 @@ export default angular.module('thingsboard.device', [
uiRouter,
thingsboardGrid,
thingsboardEvent,
thingsboardDashboardSelect,
thingsboardApiUser,
thingsboardApiDevice,
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 */
/*@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 = {
exportDashboard: exportDashboard,
importDashboard: importDashboard,
exportWidget: exportWidget,
importWidget: importWidget
importWidget: importWidget,
exportPlugin: exportPlugin,
importPlugin: importPlugin,
exportRule: exportRule,
importRule: importRule,
exportWidgetType: exportWidgetType,
importWidgetType: importWidgetType,
exportWidgetsBundle: exportWidgetsBundle,
importWidgetsBundle: importWidgetsBundle
}
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
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 thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
import thingsboardUserMenu from './user-menu.directive';
@ -72,7 +73,8 @@ export default angular.module('thingsboard.home', [
thingsboardApiLogin,
thingsboardApiUser,
thingsboardNoAnimate,
thingsboardSideMenu
thingsboardSideMenu,
thingsboardDashboardAutocomplete
])
.config(HomeRoutes)
.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",
"import": "Import 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",
"dashboard-file": "Dashboard file",
"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",
"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.",
"select-devices": "Select devices"
"select-devices": "Select devices",
"assignedToCustomer": "Assigned to customer"
},
"datakey": {
"settings": "Settings",
@ -502,7 +503,13 @@ export default angular.module('thingsboard.locale', [])
"plugin-required": "Plugin is required.",
"plugin-require-match": "Please select an existing plugin.",
"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": {
"top": "Top",
@ -559,7 +566,13 @@ export default angular.module('thingsboard.locale', [])
"create-action": "Create action",
"details": "Details",
"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": {
"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.",
"no-widgets-bundles-matching": "No widgets bundles matching '{{widgetsBundle}}' were found.",
"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": {
"data": "Data",
@ -747,6 +766,14 @@ export default angular.module('thingsboard.locale', [])
"remove-datasource": "Remove datasource",
"add-datasource": "Add datasource",
"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.
-->
<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="onSuspendPlugin({event: $event})" ng-show="!isEdit && !isReadOnly && plugin.state === 'ACTIVE'" class="md-raised md-primary">{{ 'plugin.suspend' | 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-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="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">
<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 */
/*@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 = [
{
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) {
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;
vm.types = types;
@ -82,6 +114,7 @@ export default function PluginController(pluginService, userService, $state, $st
parentCtl: vm,
actionsList: pluginActionsList,
addItemActions: pluginAddItemActionsList,
onGridInited: gridInited,
@ -107,6 +140,7 @@ export default function PluginController(pluginService, userService, $state, $st
vm.activatePlugin = activatePlugin;
vm.suspendPlugin = suspendPlugin;
vm.exportPlugin = exportPlugin;
function helpLinkIdForPlugin() {
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) {
pluginService.activatePlugin(plugin.id.id).then(function () {
vm.grid.refreshList();

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

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

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

@ -28,6 +28,7 @@
the-form="vm.grid.detailsForm"
on-activate-plugin="vm.activatePlugin(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>
</md-tab>
<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.
-->
<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="onSuspendRule({event: $event})" ng-show="!isEdit && !isReadOnly && rule.state === 'ACTIVE'" class="md-raised md-primary">{{ 'rule.suspend' | 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-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="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">
<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 */
/*@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 = [
{
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) {
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;
vm.types = types;
@ -80,6 +111,7 @@ export default function RuleController(ruleService, userService, $state, $stateP
parentCtl: vm,
actionsList: ruleActionsList,
addItemActions: ruleAddItemActionsList,
onGridInited: gridInited,
@ -104,6 +136,7 @@ export default function RuleController(ruleService, userService, $state, $stateP
vm.activateRule = activateRule;
vm.suspendRule = suspendRule;
vm.exportRule = exportRule;
function deleteRuleTitle(rule) {
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) {
ruleService.activateRule(rule.id.id).then(function () {
vm.grid.refreshList();

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

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

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

@ -28,6 +28,7 @@
the-form="vm.grid.detailsForm"
on-activate-rule="vm.activateRule(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>
</md-tab>
<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 {
padding-bottom: 8px;
}
tb-dashboard-select {
tb-dashboard-autocomplete {
@media (min-width: $layout-breakpoint-gt-sm) {
padding-right: 12px;
}

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

@ -43,16 +43,17 @@
<label translate>user.description</label>
<textarea ng-model="user.additionalInfo.description" rows="2"></textarea>
</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>
<section flex layout="column" layout-gt-sm="row">
<tb-dashboard-select flex
<tb-dashboard-autocomplete flex
ng-disabled="loading || !isEdit"
the-form="theForm"
ng-model="defaultDashboard"
ng-model="user.additionalInfo.defaultDashboardId"
dashboards-scope="customer"
customer-id="user.customerId.id"
select-first-dashboard="false">
</tb-dashboard-select>
</tb-dashboard-autocomplete>
<md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'user.always-fullscreen' | translate }}"
ng-model="user.additionalInfo.defaultDashboardFullscreen">{{ 'user.always-fullscreen' | translate }}
</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 */
/*@ngInject*/
export default function UserDirective($compile, $templateCache, dashboardService) {
export default function UserDirective($compile, $templateCache/*, dashboardService*/) {
var linker = function (scope, element) {
var template = $templateCache.get(userFieldsetTemplate);
element.html(template);
@ -32,31 +32,6 @@ export default function UserDirective($compile, $templateCache, dashboardService
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);
}
return {

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

@ -20,13 +20,14 @@ var gmGlobals = {
}
export default class TbGoogleMap {
constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey) {
constructor($containerElement, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, gmApiKey, gmDefaultMapType) {
var tbMap = this;
this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel;
this.tooltips = [];
this.defaultMapType = gmDefaultMapType;
function clearGlobalId() {
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
scrollwheel: false,
mapTypeId: getGoogleMapTypeId(tbMap.defaultMapType),
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.apiKey = gmApiKey || '';

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

@ -171,7 +171,7 @@ export default class TbMapWidget {
};
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') {
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 */
/*@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) {
var vm = this;
@ -36,6 +36,8 @@ export default function WidgetLibraryController($scope, $rootScope, $q, widgetSe
vm.dashboardInitFailed = dashboardInitFailed;
vm.addWidgetType = addWidgetType;
vm.openWidgetType = openWidgetType;
vm.exportWidgetType = exportWidgetType;
vm.importWidgetType = importWidgetType;
vm.removeWidgetType = removeWidgetType;
vm.loadWidgetLibrary = loadWidgetLibrary;
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) {
var confirm = $mdDialog.confirm()
.targetEvent(event)

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

@ -31,20 +31,45 @@
widgets="vm.widgetTypes"
is-edit="false"
is-edit-action-enabled="true"
is-export-action-enabled="true"
is-remove-action-enabled="!vm.isReadOnly()"
on-edit-widget="vm.openWidgetType(event, widget)"
on-export-widget="vm.exportWidgetType(event, widget)"
on-remove-widget="vm.removeWidgetType(event, widget)"
load-widgets="vm.loadWidgetLibrary()"
on-init="vm.dashboardInited(dashboard)"
on-init-failed="vm.dashboardInitFailed(e)">
</tb-dashboard>
<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-tooltip md-direction="top">
{{ 'widget.add-widget-type' | translate }}
</md-tooltip>
<md-icon aria-label="{{ 'action.add' | translate }}" class="material-icons">
add
</md-icon>
</md-button>
<md-fab-speed-dial ng-disabled="loading" ng-show="!vm.isReadOnly()"
md-open="vm.addItemActionsOpen" class="md-scale" md-direction="up">
<md-fab-trigger>
<md-button ng-disabled="loading"
class="tb-btn-footer md-accent md-hue-2 md-fab"
aria-label="{{ 'widget.add-widget-type' | translate }}">
<md-tooltip md-direction="top">
{{ '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>

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

@ -15,7 +15,12 @@
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">
<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 */
/*@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 = [
{
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) {
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;
vm.types = types;
@ -67,6 +98,7 @@ export default function WidgetsBundleController(widgetService, userService, $sta
parentCtl: vm,
actionsList: widgetsBundleActionsList,
addItemActions: widgetsBundleAddItemActionsList,
onGridInited: gridInited,
@ -90,6 +122,8 @@ export default function WidgetsBundleController(widgetService, userService, $sta
vm.widgetsBundleGridConfig.topIndex = $stateParams.topIndex;
}
vm.exportWidgetsBundle = exportWidgetsBundle;
function deleteWidgetsBundleTitle(widgetsBundle) {
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) {
if ($event) {
$event.stopPropagation();

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

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

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

@ -20,5 +20,7 @@
is-edit="vm.grid.detailsConfig.isDetailsEditMode"
is-read-only="vm.grid.isDetailsReadOnly(vm.grid.operatingItem())"
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>

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
***********************/

Loading…
Cancel
Save