diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index a7c427b35e..d7dd9fe482 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -460,17 +460,16 @@ public class DashboardController extends BaseController { @PathVariable("customerId") String strCustomerId, @RequestParam int pageSize, @RequestParam int page, - @RequestParam(required = false) Long startTime, - @RequestParam(required = false) Long endTime, - @RequestParam(required = false, defaultValue = "false") boolean ascOrder) throws ThingsboardException { + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("customerId", strCustomerId); try { TenantId tenantId = getCurrentUser().getTenantId(); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); checkCustomerId(customerId, Operation.READ); - TimePageLink pageLink = createTimePageLink(pageSize, page, "", - "createdTime", ascOrder ? "asc" : "desc", startTime, endTime); - return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get()); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index 2997b3d7e5..3f5d9ef882 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java @@ -323,10 +323,10 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest } List loadedDashboards = new ArrayList<>(); - TimePageLink pageLink = new TimePageLink(21); + PageLink pageLink = new PageLink(21); PageData pageData = null; do { - pageData = doGetTypedWithTimePageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", new TypeReference>(){}, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index bb49599aa4..41d91cc723 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -47,7 +47,7 @@ public interface DashboardService { void deleteDashboardsByTenantId(TenantId tenantId); - ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink); + PageData findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); void unassignCustomerDashboards(TenantId tenantId, CustomerId customerId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java index 002a07045c..867cde7328 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java @@ -47,6 +47,6 @@ public interface DashboardInfoDao extends Dao { * @param pageLink the page link * @return the list of dashboard objects */ - ListenableFuture> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink); + PageData findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 63ac5e93ca..3a5e869bfe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -188,7 +188,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - public ListenableFuture> findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TimePageLink pageLink) { + public PageData findDashboardsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findDashboardsByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(customerId, "Incorrect customerId " + customerId); @@ -250,7 +250,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } }; - private class CustomerDashboardsUnassigner extends TimePaginatedRemover { + private class CustomerDashboardsUnassigner extends PaginatedRemover { private Customer customer; @@ -259,13 +259,8 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - protected PageData findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { - try { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId()); - throw new RuntimeException(e); - } + protected PageData findEntities(TenantId tenantId, Customer customer, PageLink pageLink) { + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink); } @Override @@ -275,7 +270,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } - private class CustomerDashboardsUpdater extends TimePaginatedRemover { + private class CustomerDashboardsUpdater extends PaginatedRemover { private Customer customer; @@ -284,13 +279,8 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb } @Override - protected PageData findEntities(TenantId tenantId, Customer customer, TimePageLink pageLink) { - try { - return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink).get(); - } catch (InterruptedException | ExecutionException e) { - log.warn("Failed to get dashboards by tenantId [{}] and customerId [{}].", customer.getTenantId().getId(), customer.getId().getId()); - throw new RuntimeException(e); - } + protected PageData findEntities(TenantId tenantId, Customer customer, PageLink pageLink) { + return dashboardInfoDao.findDashboardsByTenantIdAndCustomerId(customer.getTenantId().getId(), customer.getId().getId(), pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index 34b2937150..089c5b0aad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -38,4 +38,13 @@ public interface DashboardInfoRepository extends PagingAndSortingRepository findByTenantIdAndCustomerId(@Param("tenantId") String tenantId, + @Param("customerId") String customerId, + @Param("searchText") String searchText, + Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 2b2dbf5820..0799b09e1e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -80,19 +80,12 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao> findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TimePageLink pageLink) { - log.debug("Try to find dashboards by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink); - - ListenableFuture> relations = relationDao.findRelations(new TenantId(tenantId), new CustomerId(customerId), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.DASHBOARD, EntityType.DASHBOARD, pageLink); - - return Futures.transformAsync(relations, input -> { - List> dashboardFutures = new ArrayList<>(input.getData().size()); - for (EntityRelation relation : input.getData()) { - dashboardFutures.add(findByIdAsync(new TenantId(tenantId), relation.getTo().getId())); - } - return Futures.transform(Futures.successfulAsList(dashboardFutures), dashboards -> { - return new PageData(dashboards, input.getTotalPages(), input.getTotalElements(), input.hasNext()); - }); - }); + public PageData findDashboardsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData(dashboardInfoRepository + .findByTenantIdAndCustomerId( + UUIDConverter.fromTimeUUID(tenantId), + UUIDConverter.fromTimeUUID(customerId), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index ee4bfe02cb..6c7091e178 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java @@ -302,10 +302,10 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { } List loadedDashboards = new ArrayList<>(); - TimePageLink pageLink = new TimePageLink(23); + PageLink pageLink = new PageLink(23); PageData pageData = null; do { - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -319,8 +319,8 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { dashboardService.unassignCustomerDashboards(tenantId, customerId); - pageLink = new TimePageLink(42); - pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink).get(); + pageLink = new PageLink(42); + pageData = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 00f8dabb3f..20cfca4c3f 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -992,6 +992,14 @@ } } }, + "@ngx-share/core": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@ngx-share/core/-/core-7.1.2.tgz", + "integrity": "sha512-i54tu5rS+8yxu2v+AFnssSW2FUQJEWFLUiMqXtDIzkXqlPffFyWzpkhx+vfVJi6D7zXiEq1Spb4kubeTJwZpdg==", + "requires": { + "tslib": "^1.9.0" + } + }, "@ngx-translate/core": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-11.0.1.tgz", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index b31fbebec4..6908849900 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -28,6 +28,7 @@ "@ngrx/effects": "^8.2.0", "@ngrx/store": "^8.2.0", "@ngrx/store-devtools": "^8.2.0", + "@ngx-share/core": "^7.1.2", "@ngx-translate/core": "^11.0.1", "@ngx-translate/http-loader": "^4.0.0", "ace-builds": "^1.4.5", diff --git a/ui-ngx/src/app/core/http/dashboard.service.ts b/ui-ngx/src/app/core/http/dashboard.service.ts index 3419f6ad8a..ee30421842 100644 --- a/ui-ngx/src/app/core/http/dashboard.service.ts +++ b/ui-ngx/src/app/core/http/dashboard.service.ts @@ -14,15 +14,14 @@ /// limitations under the License. /// -import { Injectable } from '@angular/core'; -import { defaultHttpOptions } from './http-utils'; -import { Observable } from 'rxjs/index'; -import { HttpClient } from '@angular/common/http'; -import { PageLink } from '@shared/models/page/page-link'; -import { PageData } from '@shared/models/page/page-data'; -import { Tenant } from '@shared/models/tenant.model'; -import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; -import {map} from 'rxjs/operators'; +import {Inject, Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {Dashboard, DashboardInfo} from '@shared/models/dashboard.models'; +import {WINDOW} from '@core/services/window.service'; @Injectable({ providedIn: 'root' @@ -30,7 +29,8 @@ import {map} from 'rxjs/operators'; export class DashboardService { constructor( - private http: HttpClient + private http: HttpClient, + @Inject(WINDOW) private window: Window ) { } public getTenantDashboards(pageLink: PageLink, ignoreErrors: boolean = false, @@ -48,14 +48,7 @@ export class DashboardService { public getCustomerDashboards(customerId: string, pageLink: PageLink, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { return this.http.get>(`/api/customer/${customerId}/dashboards${pageLink.toQuery()}`, - defaultHttpOptions(ignoreLoading, ignoreErrors)).pipe( - map( dashboards => { - dashboards.data = dashboards.data.filter(dashboard => { - return dashboard.title.toUpperCase().includes(pageLink.textSearch.toUpperCase()); - }); - return dashboards; - } - )); + defaultHttpOptions(ignoreLoading, ignoreErrors)); } public getDashboard(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { @@ -74,4 +67,61 @@ export class DashboardService { return this.http.delete(`/api/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); } + public assignDashboardToCustomer(customerId: string, dashboardId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/${customerId}/dashboard/${dashboardId}`, + null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public unassignDashboardFromCustomer(customerId: string, dashboardId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/customer/${customerId}/dashboard/${dashboardId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeDashboardPublic(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/customer/public/dashboard/${dashboardId}`, null, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public makeDashboardPrivate(dashboardId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.delete(`/api/customer/public/dashboard/${dashboardId}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public updateDashboardCustomers(dashboardId: string, customerIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/dashboard/${dashboardId}/customers`, customerIds, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public addDashboardCustomers(dashboardId: string, customerIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/dashboard/${dashboardId}/customers/add`, customerIds, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public removeDashboardCustomers(dashboardId: string, customerIds: Array, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/dashboard/${dashboardId}/customers/remove`, customerIds, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getPublicDashboardLink(dashboard: DashboardInfo): string | null { + if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { + const publicCustomers = dashboard.assignedCustomers + .filter(customerInfo => customerInfo.public); + if (publicCustomers.length > 0) { + const publicCustomerId = publicCustomers[0].customerId.id; + let url = this.window.location.protocol + '//' + this.window.location.hostname; + const port = this.window.location.port; + if (port !== '80' && port !== '443') { + url += ':' + port; + } + url += `/dashboard/${dashboard.id.id}?publicId=${publicCustomerId}`; + return url; + } + } + return null; + } + } diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index d233e6650f..adc4bf0ac2 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -107,7 +107,7 @@ export class DeviceService { } public assignDeviceToCustomer(customerId: string, deviceId: string, - ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { return this.http.post(`/api/customer/${customerId}/device/${deviceId}`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 8533b78225..1622de0d20 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -40,6 +40,7 @@ import {EntityViewService} from '@core/http/entity-view.service'; import {DataKeyType} from '@shared/models/telemetry/telemetry.models'; import {DeviceInfo} from '@shared/models/device.models'; import {defaultHttpOptions} from '@core/http/http-utils'; +import {RuleChainService} from '@core/http/rule-chain.service'; @Injectable({ providedIn: 'root' @@ -55,6 +56,7 @@ export class EntityService { private tenantService: TenantService, private customerService: CustomerService, private userService: UserService, + private ruleChainService: RuleChainService, private dashboardService: DashboardService ) { } @@ -85,7 +87,7 @@ export class EntityService { observable = this.userService.getUser(entityId, ignoreErrors, ignoreLoading); break; case EntityType.RULE_CHAIN: - // TODO: + observable = this.ruleChainService.getRuleChain(entityId, ignoreErrors, ignoreLoading); break; case EntityType.ALARM: console.error('Get Alarm Entity is not implemented!'); @@ -274,7 +276,7 @@ export class EntityService { break; case EntityType.RULE_CHAIN: pageLink.sortOrder.property = 'name'; - // TODO: + entitiesObservable = this.ruleChainService.getRuleChains(pageLink, ignoreErrors, ignoreLoading); break; case EntityType.DASHBOARD: pageLink.sortOrder.property = 'title'; diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts new file mode 100644 index 0000000000..95cb1e9900 --- /dev/null +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {RuleChain} from '@shared/models/rule-chain.models'; + +@Injectable({ + providedIn: 'root' +}) +export class RuleChainService { + + constructor( + private http: HttpClient + ) { } + + public getRuleChains(pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/ruleChains${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveRuleChain(ruleChain: RuleChain, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/ruleChain', ruleChain, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/ruleChain/${ruleChainId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public setRootRuleChain(ruleChainId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/ruleChain/${ruleChainId}/root`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/http/widget.service.ts b/ui-ngx/src/app/core/http/widget.service.ts new file mode 100644 index 0000000000..efa85f5516 --- /dev/null +++ b/ui-ngx/src/app/core/http/widget.service.ts @@ -0,0 +1,54 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; +import {defaultHttpOptions} from './http-utils'; +import {Observable} from 'rxjs/index'; +import {HttpClient} from '@angular/common/http'; +import {PageLink} from '@shared/models/page/page-link'; +import {PageData} from '@shared/models/page/page-data'; +import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; + +@Injectable({ + providedIn: 'root' +}) +export class WidgetService { + + constructor( + private http: HttpClient + ) { } + + public getWidgetBundles(pageLink: PageLink, ignoreErrors: boolean = false, + ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/widgetsBundles${pageLink.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getWidgetsBundle(widgetsBundleId: string, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveWidgetsBundle(widgetsBundle: WidgetsBundle, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/widgetsBundle', widgetsBundle, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public deleteWidgetsBundle(widgetsBundleId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { + return this.http.delete(`/api/widgetsBundle/${widgetsBundleId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + +} diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index b25fa860e9..bf848e6ceb 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -41,6 +41,17 @@ export function onParentScrollOrWindowResize(el: Node): Observable { return shared; } +export function isLocalUrl(url: string): boolean { + const parser = document.createElement('a'); + parser.href = url; + const host = parser.hostname; + if (host === 'localhost' || host === '127.0.0.1') { + return true; + } else { + return false; + } +} + const scrollRegex = /(auto|scroll)/; function parentNodes(node: Node, nodes: Node[]): Node[] { diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts index 69276418d5..cab5cd2b57 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts @@ -21,11 +21,11 @@ import {Store} from '@ngrx/store'; import {AppState} from '@core/core.state'; import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators} from '@angular/forms'; import {DeviceService} from '@core/http/device.service'; -import {EntityId} from '@shared/models/id/entity-id'; import {EntityType} from '@shared/models/entity-type.models'; import {forkJoin, Observable} from 'rxjs'; import {AssetService} from '@core/http/asset.service'; import {EntityViewService} from '@core/http/entity-view.service'; +import {DashboardService} from '@core/http/dashboard.service'; export interface AddEntitiesToCustomerDialogData { customerId: string; @@ -54,6 +54,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen private deviceService: DeviceService, private assetService: AssetService, private entityViewService: EntityViewService, + private dashboardService: DashboardService, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, public dialogRef: MatDialogRef, public fb: FormBuilder) { @@ -78,6 +79,10 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen this.assignToCustomerTitle = 'entity-view.assign-entity-view-to-customer'; this.assignToCustomerText = 'entity-view.assign-entity-view-to-customer-text'; break; + case EntityType.DASHBOARD: + this.assignToCustomerTitle = 'dashboard.assign-dashboard-to-customer'; + this.assignToCustomerText = 'dashboard.assign-dashboard-to-customer-text'; + break; } } @@ -118,6 +123,9 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen case EntityType.ENTITY_VIEW: return this.entityViewService.assignEntityViewToCustomer(customerId, entityId); break; + case EntityType.DASHBOARD: + return this.dashboardService.assignDashboardToCustomer(customerId, entityId); + break; } } diff --git a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts index 1d0c1545af..d217bc57c2 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts @@ -93,6 +93,7 @@ export class AssetsTableConfigResolver implements Resolve this.onAssetAction(action); + this.config.detailsReadonly = () => this.config.componentsData.assetScope === 'customer_user'; this.config.headerComponent = AssetTableHeaderComponent; diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 224a94a74b..d405c8cf12 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -23,6 +23,7 @@ import {UsersTableConfigResolver} from '../user/users-table-config.resolver'; import {CustomersTableConfigResolver} from './customers-table-config.resolver'; import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; import {AssetsTableConfigResolver} from '../asset/assets-table-config.resolver'; +import {DashboardsTableConfigResolver} from '@modules/home/pages/dashboard/dashboards-table-config.resolver'; const routes: Routes = [ { @@ -91,6 +92,22 @@ const routes: Routes = [ resolve: { entitiesTableConfig: AssetsTableConfigResolver } + }, + { + path: ':customerId/dashboards', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'customer.assets', + dashboardsType: 'customer', + breadcrumb: { + label: 'customer.dashboards', + icon: 'dashboard' + } + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + } } ] } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts index fa558cf0c3..7f340b9afe 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -155,6 +155,15 @@ export class CustomersTableConfigResolver implements Resolve +
+ + + + + + + +
+
+ + dashboard.assignedToCustomers + + +
+ + +
+ + dashboard.public-link + + + +
+
+
+
+ + dashboard.title + + + {{ 'dashboard.title-required' | translate }} + + +
+ + dashboard.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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. + */ + +:host { + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts new file mode 100644 index 0000000000..6914a4f0c0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts @@ -0,0 +1,110 @@ +/// +/// Copyright © 2016-2019 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 {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import { + Dashboard, + isPublicDashboard, + getDashboardAssignedCustomersText, + isCurrentPublicDashboardCustomer, + DashboardInfo +} from '@shared/models/dashboard.models'; +import {DashboardService} from '@core/http/dashboard.service'; + +@Component({ + selector: 'tb-dashboard-form', + templateUrl: './dashboard-form.component.html', + styleUrls: ['./dashboard-form.component.scss'] +}) +export class DashboardFormComponent extends EntityComponent { + + dashboardScope: 'tenant' | 'customer' | 'customer_user'; + customerId: string; + + publicLink: string; + assignedCustomersText: string; + + constructor(protected store: Store, + protected translate: TranslateService, + private dashboardService: DashboardService, + public fb: FormBuilder) { + super(store); + } + + ngOnInit() { + this.dashboardScope = this.entitiesTableConfig.componentsData.dashboardScope; + this.customerId = this.entitiesTableConfig.componentsData.customerId; + super.ngOnInit(); + } + + isPublic(entity: Dashboard): boolean { + return isPublicDashboard(entity); + } + + isCurrentPublicCustomer(entity: Dashboard): boolean { + return isCurrentPublicDashboardCustomer(entity, this.customerId); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: Dashboard): FormGroup { + this.updateFields(entity); + return this.fb.group( + { + title: [entity ? entity.title : '', [Validators.required]], + configuration: this.fb.group( + { + description: [entity && entity.configuration ? entity.configuration.description : ''], + } + ) + } + ); + } + + updateForm(entity: Dashboard) { + this.updateFields(entity); + this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({configuration: {description: entity.configuration ? entity.configuration.description : ''}}); + } + + onPublicLinkCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('dashboard.public-link-copied-message'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } + + private updateFields(entity: Dashboard): void { + this.assignedCustomersText = getDashboardAssignedCustomersText(entity); + this.publicLink = this.dashboardService.getPublicDashboardLink(entity); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts new file mode 100644 index 0000000000..bda080e3bd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-routing.module.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 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 {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; + +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {DashboardsTableConfigResolver} from './dashboards-table-config.resolver'; + +const routes: Routes = [ + { + path: 'dashboards', + data: { + breadcrumb: { + label: 'dashboard.dashboards', + icon: 'dashboard' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], + title: 'dashboard.dashboards', + dashboardsType: 'tenant' + }, + resolve: { + entitiesTableConfig: DashboardsTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + DashboardsTableConfigResolver + ] +}) +export class DashboardRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts new file mode 100644 index 0000000000..c358088524 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard.module.ts @@ -0,0 +1,41 @@ +/// +/// Copyright © 2016-2019 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 {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {HomeDialogsModule} from '../../dialogs/home-dialogs.module'; +import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component'; +import {ManageDashboardCustomersDialogComponent} from '@modules/home/pages/dashboard/manage-dashboard-customers-dialog.component'; +import {DashboardRoutingModule} from './dashboard-routing.module'; + +@NgModule({ + entryComponents: [ + DashboardFormComponent, + ManageDashboardCustomersDialogComponent + ], + declarations: [ + DashboardFormComponent, + ManageDashboardCustomersDialogComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeDialogsModule, + DashboardRoutingModule + ] +}) +export class DashboardModule { } diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts new file mode 100644 index 0000000000..b03df9a57a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -0,0 +1,484 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; +import { + CellActionDescriptor, + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig, + GroupActionDescriptor, + HeaderActionDescriptor +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {forkJoin, Observable, of} from 'rxjs'; +import {select, Store} from '@ngrx/store'; +import {selectAuthUser} from '@core/auth/auth.selectors'; +import {map, mergeMap, take, tap} from 'rxjs/operators'; +import {AppState} from '@core/core.state'; +import {Authority} from '@app/shared/models/authority.enum'; +import {CustomerService} from '@core/http/customer.service'; +import {Customer} from '@app/shared/models/customer.model'; +import {MatDialog} from '@angular/material'; +import {DialogService} from '@core/services/dialog.service'; +import { + AddEntitiesToCustomerDialogComponent, + AddEntitiesToCustomerDialogData +} from '../../dialogs/add-entities-to-customer-dialog.component'; +import { + Dashboard, + DashboardInfo, + getDashboardAssignedCustomersText, + isCurrentPublicDashboardCustomer, + isPublicDashboard +} from '@app/shared/models/dashboard.models'; +import {DashboardService} from '@app/core/http/dashboard.service'; +import {DashboardFormComponent} from '@modules/home/pages/dashboard/dashboard-form.component'; +import { + ManageDashboardCustomersActionType, + ManageDashboardCustomersDialogComponent, + ManageDashboardCustomersDialogData +} from './manage-dashboard-customers-dialog.component'; + +@Injectable() +export class DashboardsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private store: Store, + private dashboardService: DashboardService, + private customerService: CustomerService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router, + private dialog: MatDialog) { + + this.config.entityType = EntityType.DASHBOARD; + this.config.entityComponent = DashboardFormComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.DASHBOARD); + this.config.entityResources = entityTypeResources.get(EntityType.DASHBOARD); + + this.config.deleteEntityTitle = dashboard => + this.translate.instant('dashboard.delete-dashboard-title', { dashboardTitle: dashboard.title }); + this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('dashboard.delete-dashboards-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('dashboard.delete-dashboards-text'); + + this.config.loadEntity = id => this.dashboardService.getDashboard(id.id); + this.config.saveEntity = dashboard => { + return this.dashboardService.saveDashboard(dashboard as Dashboard); + }; + this.config.onEntityAction = action => this.onDashboardAction(action); + this.config.detailsReadonly = () => this.config.componentsData.dashboardScope === 'customer_user'; + } + + resolve(route: ActivatedRouteSnapshot): Observable> { + const routeParams = route.params; + this.config.componentsData = { + dashboardScope: route.data.dashboardsType, + customerId: routeParams.customerId + }; + return this.store.pipe(select(selectAuthUser), take(1)).pipe( + tap((authUser) => { + if (authUser.authority === Authority.CUSTOMER_USER) { + this.config.componentsData.dashboardScope = 'customer_user'; + this.config.componentsData.customerId = authUser.customerId; + } + }), + mergeMap(() => + this.config.componentsData.customerId ? + this.customerService.getCustomer(this.config.componentsData.customerId) : of(null as Customer) + ), + map((parentCustomer) => { + if (parentCustomer) { + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { + this.config.tableTitle = this.translate.instant('customer.public-dashboards'); + } else { + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('dashboard.dashboards'); + } + } else { + this.config.tableTitle = this.translate.instant('dashboard.dashboards'); + } + this.config.columns = this.configureColumns(this.config.componentsData.dashboardScope); + this.configureEntityFunctions(this.config.componentsData.dashboardScope); + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope); + this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.dashboardScope); + this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.dashboardScope); + this.config.addEnabled = this.config.componentsData.dashboardScope !== 'customer_user'; + this.config.entitiesDeleteEnabled = this.config.componentsData.dashboardScope === 'tenant'; + this.config.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant'; + return this.config; + }) + ); + } + + configureColumns(dashboardScope: string): Array> { + const columns: Array> = [ + new DateEntityTableColumn('createdTime', 'dashboard.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'dashboard.title') + ]; + if (dashboardScope === 'tenant') { + columns.push( + new EntityTableColumn('customersTitle', 'dashboard.assignedToCustomers', + '100%', entity => { + return getDashboardAssignedCustomersText(entity); + }, () => ({}), false), + new EntityTableColumn('dashboardIsPublic', 'dashboard.public', '60px', + entity => { + return checkBoxCell(isPublicDashboard(entity)); + }, () => ({}), false), + ); + } + return columns; + } + + configureEntityFunctions(dashboardScope: string): void { + if (dashboardScope === 'tenant') { + this.config.entitiesFetchFunction = pageLink => + this.dashboardService.getTenantDashboards(pageLink); + this.config.deleteEntity = id => this.dashboardService.deleteDashboard(id.id); + } else { + this.config.entitiesFetchFunction = pageLink => + this.dashboardService.getCustomerDashboards(this.config.componentsData.customerId, pageLink); + this.config.deleteEntity = id => + this.dashboardService.unassignDashboardFromCustomer(this.config.componentsData.customerId, id.id); + } + } + + configureCellActions(dashboardScope: string): Array> { + const actions: Array> = []; + actions.push( + { + name: this.translate.instant('dashboard.open-dashboard'), + icon: 'dashboard', + isEnabled: () => true, + onAction: ($event, entity) => this.openDashboard($event, entity) + } + ); + if (dashboardScope === 'tenant') { + actions.push( + { + name: this.translate.instant('dashboard.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportDashboard($event, entity) + }, + { + name: this.translate.instant('dashboard.make-public'), + icon: 'share', + isEnabled: (entity) => !isPublicDashboard(entity), + onAction: ($event, entity) => this.makePublic($event, entity) + }, + { + name: this.translate.instant('dashboard.make-private'), + icon: 'reply', + isEnabled: (entity) => isPublicDashboard(entity), + onAction: ($event, entity) => this.makePrivate($event, entity) + }, + { + name: this.translate.instant('dashboard.manage-assigned-customers'), + icon: 'assignment_ind', + isEnabled: () => true, + onAction: ($event, entity) => this.manageAssignedCustomers($event, entity) + } + ); + } + if (dashboardScope === 'customer') { + actions.push( + { + name: this.translate.instant('dashboard.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportDashboard($event, entity) + }, + { + name: this.translate.instant('dashboard.make-private'), + icon: 'reply', + isEnabled: (entity) => isCurrentPublicDashboardCustomer(entity, this.config.componentsData.customerId), + onAction: ($event, entity) => this.makePrivate($event, entity) + }, + { + name: this.translate.instant('dashboard.unassign-from-customer'), + icon: 'assignment_return', + isEnabled: (entity) => !isCurrentPublicDashboardCustomer(entity, this.config.componentsData.customerId), + onAction: ($event, entity) => this.unassignFromCustomer($event, entity, this.config.componentsData.customerId) + } + ); + } + return actions; + } + + configureGroupActions(dashboardScope: string): Array> { + const actions: Array> = []; + if (dashboardScope === 'tenant') { + actions.push( + { + name: this.translate.instant('dashboard.assign-dashboards'), + icon: 'assignment_ind', + isEnabled: true, + onAction: ($event, entities) => this.assignDashboardsToCustomers($event, entities.map((entity) => entity.id.id)) + } + ); + actions.push( + { + name: this.translate.instant('dashboard.unassign-dashboards'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => this.unassignDashboardsFromCustomers($event, entities.map((entity) => entity.id.id)) + } + ); + } + if (dashboardScope === 'customer') { + actions.push( + { + name: this.translate.instant('dashboard.unassign-dashboards'), + icon: 'assignment_return', + isEnabled: true, + onAction: ($event, entities) => + this.unassignDashboardsFromCustomer($event, entities.map((entity) => entity.id.id), this.config.componentsData.customerId) + } + ); + } + return actions; + } + + configureAddActions(dashboardScope: string): Array { + const actions: Array = []; + if (dashboardScope === 'tenant') { + actions.push( + { + name: this.translate.instant('dashboard.create-new-dashboard'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('dashboard.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importDashboard($event) + } + ); + } + if (dashboardScope === 'customer') { + actions.push( + { + name: this.translate.instant('dashboard.assign-new-dashboard'), + icon: 'add', + isEnabled: () => true, + onAction: ($event) => this.addDashboardsToCustomer($event) + } + ); + } + return actions; + } + + openDashboard($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + // this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + importDashboard($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + exportDashboard($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + addDashboardsToCustomer($event: Event) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(AddEntitiesToCustomerDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + customerId: this.config.componentsData.customerId, + entityType: EntityType.DASHBOARD + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + makePublic($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + this.dashboardService.makeDashboardPublic(dashboard.id.id).subscribe( + (publicDashboard) => { + // TODO: + + this.config.table.updateData(); + } + ); + } + + makePrivate($event: Event, dashboard: DashboardInfo) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title}), + this.translate.instant('dashboard.make-private-dashboard-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.dashboardService.makeDashboardPrivate(dashboard.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + manageAssignedCustomers($event: Event, dashboard: DashboardInfo) { + const assignedCustomersIds = dashboard.assignedCustomers ? + dashboard.assignedCustomers.map(customerInfo => customerInfo.customerId.id) : []; + this.showManageAssignedCustomersDialog($event, [dashboard.id.id], 'manage', assignedCustomersIds); + } + + assignDashboardsToCustomers($event: Event, dashboardIds: Array) { + this.showManageAssignedCustomersDialog($event, dashboardIds, 'assign'); + } + + unassignDashboardsFromCustomers($event: Event, dashboardIds: Array) { + this.showManageAssignedCustomersDialog($event, dashboardIds, 'unassign'); + } + + showManageAssignedCustomersDialog($event: Event, dashboardIds: Array, + actionType: ManageDashboardCustomersActionType, + assignedCustomersIds?: Array) { + if ($event) { + $event.stopPropagation(); + } + this.dialog.open(ManageDashboardCustomersDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: { + dashboardIds, + actionType, + assignedCustomersIds + } + }).afterClosed() + .subscribe((res) => { + if (res) { + this.config.table.updateData(); + } + }); + } + + unassignFromCustomer($event: Event, dashboard: DashboardInfo, customerId: string) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title}), + this.translate.instant('dashboard.unassign-dashboard-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + unassignDashboardsFromCustomer($event: Event, dashboardIds: Array, customerId: string) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('dashboard.unassign-dashboards-title', {count: dashboardIds.length}), + this.translate.instant('dashboard.unassign-dashboards-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + const tasks: Observable[] = []; + dashboardIds.forEach( + (dashboardId) => { + tasks.push(this.dashboardService.unassignDashboardFromCustomer(customerId, dashboardId)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onDashboardAction(action: EntityAction): boolean { + switch (action.action) { + case 'open': + this.openDashboard(action.event, action.entity); + return true; + case 'export': + this.exportDashboard(action.event, action.entity); + return true; + case 'makePublic': + this.makePublic(action.event, action.entity); + return true; + case 'makePrivate': + this.makePrivate(action.event, action.entity); + return true; + case 'manageAssignedCustomers': + this.manageAssignedCustomers(action.event, action.entity); + return true; + case 'unassignFromCustomer': + this.unassignFromCustomer(action.event, action.entity, this.config.componentsData.customerId); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html new file mode 100644 index 0000000000..6f3cbb9171 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html @@ -0,0 +1,58 @@ + +
+ +

{{ titleText | translate }}

+ + +
+ + +
+
+
+ {{ labelText | translate }} + + +
+
+
+ + + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts new file mode 100644 index 0000000000..402f20a958 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts @@ -0,0 +1,129 @@ +/// +/// Copyright © 2016-2019 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 {Component, Inject, OnInit, SkipSelf} from '@angular/core'; +import {ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material'; +import {PageComponent} from '@shared/components/page.component'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {DashboardService} from '@core/http/dashboard.service'; +import {forkJoin, Observable} from 'rxjs'; + +export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign'; + +export interface ManageDashboardCustomersDialogData { + actionType: ManageDashboardCustomersActionType; + dashboardIds: Array; + assignedCustomersIds?: Array; +} + +@Component({ + selector: 'tb-manage-dashboard-customers-dialog', + templateUrl: './manage-dashboard-customers-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}], + styleUrls: [] +}) +export class ManageDashboardCustomersDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { + + dashboardCustomersFormGroup: FormGroup; + + submitted = false; + + entityType = EntityType; + + titleText: string; + labelText: string; + actionName: string; + + assignedCustomersIds: string[]; + + constructor(protected store: Store, + @Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData, + private dashboardService: DashboardService, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher, + public dialogRef: MatDialogRef, + public fb: FormBuilder) { + super(store); + + this.assignedCustomersIds = data.assignedCustomersIds || []; + switch (data.actionType) { + case 'assign': + this.titleText = 'dashboard.assign-to-customers'; + this.labelText = 'dashboard.assign-to-customers-text'; + this.actionName = 'action.assign'; + break; + case 'manage': + this.titleText = 'dashboard.manage-assigned-customers'; + this.labelText = 'dashboard.assigned-customers'; + this.actionName = 'action.update'; + break; + case 'unassign': + this.titleText = 'dashboard.unassign-from-customers'; + this.labelText = 'dashboard.unassign-from-customers-text'; + this.actionName = 'action.unassign'; + break; + } + } + + ngOnInit(): void { + this.dashboardCustomersFormGroup = this.fb.group({ + assignedCustomerIds: [[...this.assignedCustomersIds]] + }); + } + + isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(false); + } + + submit(): void { + this.submitted = true; + const customerIds: Array = this.dashboardCustomersFormGroup.get('assignedCustomerIds').value; + const tasks: Observable[] = []; + + this.data.dashboardIds.forEach( + (dashboardId) => { + tasks.push(this.getManageDashboardCustomersTask(dashboardId, customerIds)); + } + ); + forkJoin(tasks).subscribe( + () => { + this.dialogRef.close(true); + } + ); + } + + private getManageDashboardCustomersTask(dashboardId: string, customerIds: Array): Observable { + switch (this.data.actionType) { + case 'assign': + return this.dashboardService.addDashboardCustomers(dashboardId, customerIds); + break; + case 'manage': + return this.dashboardService.updateDashboardCustomers(dashboardId, customerIds); + break; + case 'unassign': + return this.dashboardService.removeDashboardCustomers(dashboardId, customerIds); + break; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts index ad1bb3a820..4c22a3b440 100644 --- a/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts @@ -96,6 +96,7 @@ export class DevicesTableConfigResolver implements Resolve this.onDeviceAction(action); + this.config.detailsReadonly = () => this.config.componentsData.deviceScope === 'customer_user'; this.config.headerComponent = DeviceTableHeaderComponent; diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html index af0ce120a6..de884e1b9a 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html @@ -85,7 +85,6 @@ formControlName="entityId"> -
diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts index fcaa8aa4c5..60a2421c7b 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts @@ -96,6 +96,7 @@ export class EntityViewsTableConfigResolver implements Resolve this.onEntityViewAction(action); + this.config.detailsReadonly = () => this.config.componentsData.entityViewScope === 'customer_user'; this.config.headerComponent = EntityViewTableHeaderComponent; diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index 7ff956ddc1..ee66f2bb76 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -26,6 +26,9 @@ import { UserModule } from '@modules/home/pages/user/user.module'; import {DeviceModule} from '@modules/home/pages/device/device.module'; import {AssetModule} from '@modules/home/pages/asset/asset.module'; import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.module'; +import {RuleChainModule} from '@modules/home/pages/rulechain/rulechain.module'; +import {WidgetLibraryModule} from '@modules/home/pages/widget/widget-library.module'; +import {DashboardModule} from '@modules/home/pages/dashboard/dashboard.module'; @NgModule({ exports: [ @@ -37,6 +40,9 @@ import {EntityViewModule} from '@modules/home/pages/entity-view/entity-view.modu AssetModule, EntityViewModule, CustomerModule, + RuleChainModule, + WidgetLibraryModule, + DashboardModule, // AuditLogModule, UserModule ] diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts new file mode 100644 index 0000000000..207c1b6225 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-routing.module.ts @@ -0,0 +1,56 @@ +/// +/// Copyright © 2016-2019 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 {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; + +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; + +const routes: Routes = [ + { + path: 'ruleChains', + data: { + breadcrumb: { + label: 'rulechain.rulechains', + icon: 'settings_ethernet' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'rulechain.rulechains' + }, + resolve: { + entitiesTableConfig: RuleChainsTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + RuleChainsTableConfigResolver + ] +}) +export class RuleChainRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html new file mode 100644 index 0000000000..7a2f485261 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.html @@ -0,0 +1,75 @@ + +
+ + + + +
+ +
+
+
+
+
+ + rulechain.name + + + {{ 'rulechain.name-required' | translate }} + + + + {{ 'rulechain.debug-mode' | translate }} + +
+ + rulechain.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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. + */ + +:host { + +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts new file mode 100644 index 0000000000..f055099a87 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts @@ -0,0 +1,81 @@ +/// +/// Copyright © 2016-2019 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 {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {EntityType} from '@shared/models/entity-type.models'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {ActionNotificationShow} from '@core/notification/notification.actions'; +import {TranslateService} from '@ngx-translate/core'; +import {AssetInfo} from '@app/shared/models/asset.models'; +import {RuleChain} from "@shared/models/rule-chain.models"; + +@Component({ + selector: 'tb-rulechain', + templateUrl: './rulechain.component.html', + styleUrls: ['./rulechain.component.scss'] +}) +export class RuleChainComponent extends EntityComponent { + + constructor(protected store: Store, + protected translate: TranslateService, + public fb: FormBuilder) { + super(store); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: RuleChain): FormGroup { + return this.fb.group( + { + name: [entity ? entity.name : '', [Validators.required]], + debugMode: [entity ? entity.debugMode : false], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + } + ); + } + + updateForm(entity: RuleChain) { + this.entityForm.patchValue({name: entity.name}); + this.entityForm.patchValue({debugMode: entity.debugMode}); + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); + } + + + onRuleChainIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('rulechain.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts new file mode 100644 index 0000000000..75b72cb789 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.component'; +import {RuleChainRoutingModule} from '@modules/home/pages/rulechain/rulechain-routing.module'; + +@NgModule({ + entryComponents: [ + RuleChainComponent + ], + declarations: [ + RuleChainComponent + ], + imports: [ + CommonModule, + SharedModule, + RuleChainRoutingModule + ] +}) +export class RuleChainModule { } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts new file mode 100644 index 0000000000..9eea7a6780 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -0,0 +1,176 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {Resolve, Router} from '@angular/router'; +import { + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {RuleChain} from '@shared/models/rule-chain.models'; +import {RuleChainService} from '@core/http/rule-chain.service'; +import {RuleChainComponent} from '@modules/home/pages/rulechain/rulechain.component'; +import {DialogService} from '@core/services/dialog.service'; + +@Injectable() +export class RuleChainsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private ruleChainService: RuleChainService, + private dialogService: DialogService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.RULE_CHAIN; + this.config.entityComponent = RuleChainComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.RULE_CHAIN); + this.config.entityResources = entityTypeResources.get(EntityType.RULE_CHAIN); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'rulechain.created-time', this.datePipe, '150px'), + new EntityTableColumn('name', 'rulechain.name'), + new EntityTableColumn('root', 'rulechain.root', '60px', + entity => { + return checkBoxCell(entity.root); + }), + ); + + this.config.addActionDescriptors.push( + { + name: this.translate.instant('rulechain.create-new-rulechain'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('rulechain.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importRuleChain($event) + } + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('rulechain.open-rulechain'), + icon: 'settings_ethernet', + isEnabled: () => true, + onAction: ($event, entity) => this.openRuleChain($event, entity) + }, + { + name: this.translate.instant('rulechain.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportRuleChain($event, entity) + }, + { + name: this.translate.instant('rulechain.set-root'), + icon: 'flag', + isEnabled: (ruleChain) => !ruleChain.root, + onAction: ($event, entity) => this.setRootRuleChain($event, entity) + } + ); + + this.config.deleteEntityTitle = ruleChain => this.translate.instant('rulechain.delete-rulechain-title', + { ruleChainName: ruleChain.name }); + this.config.deleteEntityContent = () => this.translate.instant('rulechain.delete-rulechain-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('rulechain.delete-rulechains-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('rulechain.delete-rulechains-text'); + + this.config.entitiesFetchFunction = pageLink => this.ruleChainService.getRuleChains(pageLink); + this.config.loadEntity = id => this.ruleChainService.getRuleChain(id.id); + this.config.saveEntity = ruleChain => this.ruleChainService.saveRuleChain(ruleChain); + this.config.deleteEntity = id => this.ruleChainService.deleteRuleChain(id.id); + this.config.onEntityAction = action => this.onRuleChainAction(action); + this.config.deleteEnabled = (ruleChain) => ruleChain && !ruleChain.root; + this.config.entitySelectionEnabled = (ruleChain) => ruleChain && !ruleChain.root; + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('rulechain.rulechains'); + + return this.config; + } + + importRuleChain($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + openRuleChain($event: Event, ruleChain: RuleChain) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + // this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + exportRuleChain($event: Event, ruleChain: RuleChain) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + setRootRuleChain($event: Event, ruleChain: RuleChain) { + if ($event) { + $event.stopPropagation(); + } + this.dialogService.confirm( + this.translate.instant('rulechain.set-root-rulechain-title', {ruleChainName: ruleChain.name}), + this.translate.instant('rulechain.set-root-rulechain-text'), + this.translate.instant('action.no'), + this.translate.instant('action.yes'), + true + ).subscribe((res) => { + if (res) { + this.ruleChainService.setRootRuleChain(ruleChain.id.id).subscribe( + () => { + this.config.table.updateData(); + } + ); + } + } + ); + } + + onRuleChainAction(action: EntityAction): boolean { + switch (action.action) { + case 'open': + this.openRuleChain(action.event, action.entity); + return true; + case 'export': + this.exportRuleChain(action.event, action.entity); + return true; + case 'setRoot': + this.setRootRuleChain(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts new file mode 100644 index 0000000000..787ea3d420 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts @@ -0,0 +1,57 @@ +/// +/// Copyright © 2016-2019 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 {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; + +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; +import {Authority} from '@shared/models/authority.enum'; +import {RuleChainsTableConfigResolver} from '@modules/home/pages/rulechain/rulechains-table-config.resolver'; +import {WidgetsBundlesTableConfigResolver} from '@modules/home/pages/widget/widgets-bundles-table-config.resolver'; + +const routes: Routes = [ + { + path: 'widgets-bundles', + data: { + breadcrumb: { + label: 'widgets-bundle.widgets-bundles', + icon: 'now_widgets' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + title: 'widgets-bundle.widgets-bundles' + }, + resolve: { + entitiesTableConfig: WidgetsBundlesTableConfigResolver + } + } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + WidgetsBundlesTableConfigResolver + ] +}) +export class WidgetLibraryRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts new file mode 100644 index 0000000000..727f985738 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-library.module.ts @@ -0,0 +1,36 @@ +/// +/// Copyright © 2016-2019 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 {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {SharedModule} from '@shared/shared.module'; +import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle.component'; +import {WidgetLibraryRoutingModule} from '@modules/home/pages/widget/widget-library-routing.module'; + +@NgModule({ + entryComponents: [ + WidgetsBundleComponent + ], + declarations: [ + WidgetsBundleComponent + ], + imports: [ + CommonModule, + SharedModule, + WidgetLibraryRoutingModule + ] +}) +export class WidgetLibraryModule { } diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html new file mode 100644 index 0000000000..1af44ff331 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.html @@ -0,0 +1,44 @@ + +
+ + +
+
+
+
+ + widgets-bundle.title + + + {{ 'widgets-bundle.title-required' | translate }} + + +
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss new file mode 100644 index 0000000000..d18a4874d0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.scss @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2019 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. + */ + +:host { + +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts new file mode 100644 index 0000000000..16f908c1b0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts @@ -0,0 +1,55 @@ +/// +/// Copyright © 2016-2019 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 {Component} from '@angular/core'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {EntityComponent} from '@shared/components/entity/entity.component'; +import {FormBuilder, FormGroup, Validators} from '@angular/forms'; +import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; + +@Component({ + selector: 'tb-widgets-bundle', + templateUrl: './widgets-bundle.component.html', + styleUrls: ['./widgets-bundle.component.scss'] +}) +export class WidgetsBundleComponent extends EntityComponent { + + constructor(protected store: Store, + public fb: FormBuilder) { + super(store); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: WidgetsBundle): FormGroup { + return this.fb.group( + { + title: [entity ? entity.title : '', [Validators.required]] + } + ); + } + + updateForm(entity: WidgetsBundle) { + this.entityForm.patchValue({title: entity.title}); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts new file mode 100644 index 0000000000..aea0fedff3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts @@ -0,0 +1,158 @@ +/// +/// Copyright © 2016-2019 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 {Injectable} from '@angular/core'; + +import {Resolve, Router} from '@angular/router'; +import { + checkBoxCell, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@shared/components/entity/entities-table-config.models'; +import {TranslateService} from '@ngx-translate/core'; +import {DatePipe} from '@angular/common'; +import {EntityType, entityTypeResources, entityTypeTranslations} from '@shared/models/entity-type.models'; +import {EntityAction} from '@shared/components/entity/entity-component.models'; +import {WidgetsBundle} from '@shared/models/widgets-bundle.model'; +import {WidgetService} from '@app/core/http/widget.service'; +import {WidgetsBundleComponent} from '@modules/home/pages/widget/widgets-bundle.component'; +import {NULL_UUID} from '@shared/models/id/has-uuid'; +import {Store} from '@ngrx/store'; +import {AppState} from '@core/core.state'; +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; +import {Authority} from '@shared/models/authority.enum'; + +@Injectable() +export class WidgetsBundlesTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private store: Store, + private widgetsService: WidgetService, + private translate: TranslateService, + private datePipe: DatePipe, + private router: Router) { + + this.config.entityType = EntityType.WIDGETS_BUNDLE; + this.config.entityComponent = WidgetsBundleComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.WIDGETS_BUNDLE); + this.config.entityResources = entityTypeResources.get(EntityType.WIDGETS_BUNDLE); + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'widgets-bundle.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'widgets-bundle.title'), + new EntityTableColumn('tenantId', 'widgets-bundle.system', '60px', + entity => { + return checkBoxCell(entity.tenantId.id === NULL_UUID); + }), + ); + + this.config.addActionDescriptors.push( + { + name: this.translate.instant('widgets-bundle.create-new-widgets-bundle'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('widgets-bundle.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importWidgetsBundle($event) + } + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('widgets-bundle.open-widgets-bundle'), + icon: 'now_widgets', + isEnabled: () => true, + onAction: ($event, entity) => this.openWidgetsBundle($event, entity) + }, + { + name: this.translate.instant('widgets-bundle.export'), + icon: 'file_download', + isEnabled: () => true, + onAction: ($event, entity) => this.exportWidgetsBundle($event, entity) + } + ); + + this.config.deleteEntityTitle = widgetsBundle => this.translate.instant('widgets-bundle.delete-widgets-bundle-title', + { widgetsBundleTitle: widgetsBundle.title }); + this.config.deleteEntityContent = () => this.translate.instant('widgets-bundle.delete-widgets-bundle-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('widgets-bundle.delete-widgets-bundles-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('widgets-bundle.delete-widgets-bundles-text'); + + this.config.entitiesFetchFunction = pageLink => this.widgetsService.getWidgetBundles(pageLink); + this.config.loadEntity = id => this.widgetsService.getWidgetsBundle(id.id); + this.config.saveEntity = widgetsBundle => this.widgetsService.saveWidgetsBundle(widgetsBundle); + this.config.deleteEntity = id => this.widgetsService.deleteWidgetsBundle(id.id); + this.config.onEntityAction = action => this.onWidgetsBundleAction(action); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('widgets-bundle.widgets-bundles'); + const authUser = getCurrentAuthUser(this.store); + this.config.deleteEnabled = (widgetsBundle) => this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); + this.config.entitySelectionEnabled = (widgetsBundle) => this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); + this.config.detailsReadonly = (widgetsBundle) => !this.isWidgetsBundleEditable(widgetsBundle, authUser.authority); + return this.config; + } + + isWidgetsBundleEditable(widgetsBundle: WidgetsBundle, authority: Authority): boolean { + if (authority === Authority.TENANT_ADMIN) { + return widgetsBundle && widgetsBundle.tenantId && widgetsBundle.tenantId.id !== NULL_UUID; + } else { + return authority === Authority.SYS_ADMIN; + } + } + + importWidgetsBundle($event: Event) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + openWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + // this.router.navigateByUrl(`customers/${customer.id.id}/users`); + } + + exportWidgetsBundle($event: Event, widgetsBundle: WidgetsBundle) { + if ($event) { + $event.stopPropagation(); + } + // TODO: + } + + onWidgetsBundleAction(action: EntityAction): boolean { + switch (action.action) { + case 'open': + this.openWidgetsBundle(action.event, action.entity); + return true; + case 'export': + this.exportWidgetsBundle(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html index d7b21e99d3..0884a92e53 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.html @@ -19,6 +19,7 @@ + + + +
diff --git a/ui-ngx/src/app/shared/components/socialshare-panel.component.ts b/ui-ngx/src/app/shared/components/socialshare-panel.component.ts new file mode 100644 index 0000000000..55de4477cd --- /dev/null +++ b/ui-ngx/src/app/shared/components/socialshare-panel.component.ts @@ -0,0 +1,61 @@ +/// +/// Copyright © 2016-2019 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 { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { User } from '@shared/models/user.model'; +import { Authority } from '@shared/models/authority.enum'; +import { select, Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { selectAuthUser, selectUserDetails } from '@core/auth/auth.selectors'; +import { map } from 'rxjs/operators'; +import { AuthService } from '@core/auth/auth.service'; +import { Router } from '@angular/router'; +import {isLocalUrl} from '@core/utils'; + +@Component({ + selector: 'tb-social-share-panel', + templateUrl: './socialshare-panel.component.html', + styleUrls: [] +}) +export class SocialSharePanelComponent implements OnInit { + + @Input() + shareTitle: string; + + @Input() + shareText: string; + + @Input() + shareLink: string; + + @Input() + shareHashTags: string; + + constructor() { + } + + ngOnInit(): void { + } + + isShareLinkLocal(): boolean { + if (this.shareLink && this.shareLink.length > 0) { + return isLocalUrl(this.shareLink); + } else { + return true; + } + } + +} diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index c6976ff660..b3538306d7 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -62,7 +62,10 @@ export const HelpLinks = { users: helpBaseUrl + '/docs/user-guide/ui/users', devices: helpBaseUrl + '/docs/user-guide/ui/devices', assets: helpBaseUrl + '/docs/user-guide/ui/assets', - entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views' + entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', + rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', + dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', + widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles' } }; diff --git a/ui-ngx/src/app/shared/models/customer.model.ts b/ui-ngx/src/app/shared/models/customer.model.ts index adfa684fe0..502abad934 100644 --- a/ui-ngx/src/app/shared/models/customer.model.ts +++ b/ui-ngx/src/app/shared/models/customer.model.ts @@ -27,5 +27,5 @@ export interface Customer extends ContactBased { export interface ShortCustomerInfo { customerId: CustomerId; title: string; - isPublic: boolean; + public: boolean; } diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index ce99407541..6204818ca4 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -26,10 +26,40 @@ export interface DashboardInfo extends BaseData { } export interface DashboardConfiguration { - widgets: Array; + [key: string]: any; // TODO: } export interface Dashboard extends DashboardInfo { configuration: DashboardConfiguration; } + +export function isPublicDashboard(dashboard: DashboardInfo): boolean { + if (dashboard && dashboard.assignedCustomers) { + return dashboard.assignedCustomers + .filter(customerInfo => customerInfo.public).length > 0; + } else { + return false; + } +} + +export function getDashboardAssignedCustomersText(dashboard: DashboardInfo): string { + if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { + return dashboard.assignedCustomers + .filter(customerInfo => !customerInfo.public) + .map(customerInfo => customerInfo.title) + .join(', '); + } else { + return null; + } +} + +export function isCurrentPublicDashboardCustomer(dashboard: DashboardInfo, customerId: string): boolean { + if (customerId && dashboard && dashboard.assignedCustomers) { + return dashboard.assignedCustomers.filter(customerInfo => { + return customerInfo.public && customerId === customerInfo.customerId.id; + }).length > 0; + } else { + return false; + } +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 70588c2e8b..1d8b72cb0f 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -36,9 +36,9 @@ export enum AliasEntityType { } export interface EntityTypeTranslation { - type: string; + type?: string; typePlural?: string; - list: string; + list?: string; nameStartsWith?: string; details?: string; add?: string; @@ -137,6 +137,44 @@ export const entityTypeTranslations = new Map( { helpLinkId: 'entityViews' } + ], + [ + EntityType.RULE_CHAIN, + { + helpLinkId: 'rulechains' + } + ], + [ + EntityType.DASHBOARD, + { + helpLinkId: 'dashboards' + } + ], + [ + EntityType.WIDGETS_BUNDLE, + { + helpLinkId: 'widgetsBundles' + } ] ] ); diff --git a/ui-ngx/src/app/shared/models/rule-chain.models.ts b/ui-ngx/src/app/shared/models/rule-chain.models.ts index 52d396d888..100214a8cc 100644 --- a/ui-ngx/src/app/shared/models/rule-chain.models.ts +++ b/ui-ngx/src/app/shared/models/rule-chain.models.ts @@ -15,23 +15,16 @@ /// import {BaseData} from '@shared/models/base-data'; -import {AssetId} from '@shared/models/id/asset-id'; import {TenantId} from '@shared/models/id/tenant-id'; -import {CustomerId} from '@shared/models/id/customer-id'; import {RuleChainId} from '@shared/models/id/rule-chain-id'; import {RuleNodeId} from '@shared/models/id/rule-node-id'; -export interface RuleChainConfiguration { - todo: Array; - // TODO: -} - export interface RuleChain extends BaseData { tenantId: TenantId; name: string; firstRuleNodeId: RuleNodeId; root: boolean; debugMode: boolean; - configuration: RuleChainConfiguration; + configuration?: any; additionalInfo?: any; } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index f1ea7825ce..feab000ddc 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -55,6 +55,7 @@ import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimep import { FlexLayoutModule } from '@angular/flex-layout'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; +import { ShareModule as ShareButtonsModule } from '@ngx-share/core'; import { UserMenuComponent } from '@shared/components/user-menu.component'; import { NospacePipe } from './pipe/nospace.pipe'; import { TranslateModule } from '@ngx-translate/core'; @@ -88,6 +89,7 @@ import {EntityTypeSelectComponent} from './components/entity/entity-type-select. import {EntitySelectComponent} from './components/entity/entity-select.component'; import {DatetimeComponent} from '@shared/components/time/datetime.component'; import {EntityKeysListComponent} from './components/entity/entity-keys-list.component'; +import {SocialSharePanelComponent} from './components/socialshare-panel.component'; @NgModule({ providers: [ @@ -136,6 +138,7 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp EntityTypeSelectComponent, EntitySelectComponent, EntityKeysListComponent, + SocialSharePanelComponent, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, @@ -179,7 +182,8 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp FlexLayoutModule.withConfig({addFlexToParent: false}), FormsModule, ReactiveFormsModule, - OverlayModule + OverlayModule, + ShareButtonsModule ], exports: [ FooterComponent, @@ -210,6 +214,7 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp EntityTypeSelectComponent, EntitySelectComponent, EntityKeysListComponent, + SocialSharePanelComponent, // ValueInputComponent, MatButtonModule, MatCheckboxModule, @@ -246,6 +251,7 @@ import {EntityKeysListComponent} from './components/entity/entity-keys-list.comp FormsModule, ReactiveFormsModule, OverlayModule, + ShareButtonsModule, NospacePipe, MillisecondsToTimeStringPipe, EnumToArrayPipe, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 53d03ac648..e1f090b863 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -445,6 +445,7 @@ "no-dashboards-text": "No dashboards found", "no-widgets": "No widgets configured", "add-widget": "Add new widget", + "created-time": "Created time", "title": "Title", "select-widget-title": "Select widget", "select-widget-subtitle": "List of available widget types", @@ -562,7 +563,9 @@ "show-details": "Show details", "hide-details": "Hide details", "select-state": "Select target state", - "state-controller": "State controller" + "state-controller": "State controller", + "search": "Search dashboards", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selected" }, "datakey": { "settings": "Settings", @@ -1282,6 +1285,7 @@ "rulechains": "Rule chains", "root": "Root", "delete": "Delete rule chain", + "created-time": "Created time", "name": "Name", "name-required": "Name is required.", "description": "Description", @@ -1312,7 +1316,10 @@ "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.", "rulechain-required": "Rule chain is required", "management": "Rules management", - "debug-mode": "Debug mode" + "debug-mode": "Debug mode", + "search": "Search rule chains", + "selected-rulechains": "{ count, plural, 1 {1 rule chain} other {# rule chains} } selected", + "open-rulechain": "Open rule chain" }, "rulenode": { "details": "Details", @@ -1565,6 +1572,7 @@ "widgets-bundles": "Widgets Bundles", "add": "Add Widgets Bundle", "delete": "Delete widgets bundle", + "created-time": "Created time", "title": "Title", "title-required": "Title is required.", "add-widgets-bundle-text": "Add new widgets bundle", @@ -1585,7 +1593,10 @@ "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." + "invalid-widgets-bundle-file-error": "Unable to import widgets bundle: Invalid widgets bundle data structure.", + "search": "Search widget bundles", + "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selected", + "open-widgets-bundle": "Open widgets bundle" }, "widget-config": { "data": "Data",