Merge remote-tracking branch 'deaflynx/develop/3.3-edge' into develop/3.3-edge

This commit is contained in:
Volodymyr Babak 2020-12-14 12:55:07 +02:00
commit 846f5ba86b
39 changed files with 355 additions and 80 deletions

View File

@ -492,6 +492,7 @@ public class AssetController extends BaseController {
@PathVariable(EDGE_ID) String strEdgeId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@ -503,7 +504,11 @@ public class AssetController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
} else {
return checkNotNull(assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}

View File

@ -677,6 +677,7 @@ public class EntityViewController extends BaseController {
@PathVariable(EDGE_ID) String strEdgeId,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@ -688,7 +689,11 @@ public class EntityViewController extends BaseController {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
if (type != null && type.trim().length() > 0) {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
} else {
return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
}
} catch (Exception e) {
throw handleException(e);
}

View File

@ -82,4 +82,6 @@ public interface AssetService {
Asset unassignAssetFromEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId);
PageData<Asset> findAssetsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
PageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TimePageLink pageLink);
}

View File

@ -83,4 +83,6 @@ public interface EntityViewService {
EntityView unassignEntityViewFromEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId);
PageData<EntityView> findEntityViewsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
PageData<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TimePageLink pageLink);
}

View File

@ -177,4 +177,15 @@ public interface AssetDao extends Dao<Asset>, TenantEntityDao {
* @return the list of asset objects
*/
PageData<Asset> findAssetsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, TimePageLink pageLink);
/**
* Find assets by tenantId, edgeId, type and page link.
*
* @param tenantId the tenantId
* @param edgeId the edgeId
* @param type the type
* @param pageLink the page link
* @return the list of asset objects
*/
PageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TimePageLink pageLink);
}

View File

@ -372,6 +372,16 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
return assetDao.findAssetsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
}
@Override
public PageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TimePageLink pageLink) {
log.trace("Executing findAssetsByTenantIdAndEdgeIdAndType, tenantId [{}], edgeId [{}], type [{}] pageLink [{}]", tenantId, edgeId, type, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
validateString(type, "Incorrect type " + type);
validatePageLink(pageLink);
return assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
}
private DataValidator<Asset> assetValidator =
new DataValidator<Asset>() {

View File

@ -165,4 +165,18 @@ public interface EntityViewDao extends Dao<EntityView> {
UUID edgeId,
PageLink pageLink);
/**
* Find entity views by tenantId, edgeId, type and page link.
*
* @param tenantId the tenantId
* @param edgeId the edgeId
* @param type the type
* @param pageLink the page link
* @return the list of entity view objects
*/
PageData<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(UUID tenantId,
UUID edgeId,
String type,
PageLink pageLink);
}

View File

@ -390,6 +390,16 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return entityViewDao.findEntityViewsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
}
@Override
public PageData<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, TimePageLink pageLink) {
log.trace("Executing findEntityViewsByTenantIdAndEdgeIdAndType, tenantId [{}], edgeId [{}], type [{}], pageLink [{}]", tenantId, edgeId, type, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(edgeId, INCORRECT_EDGE_ID + edgeId);
validateString(type, "Incorrect type " + type);
validatePageLink(pageLink);
return entityViewDao.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
}
private DataValidator<EntityView> entityViewValidator =
new DataValidator<EntityView>() {

View File

@ -132,5 +132,16 @@ public interface AssetRepository extends PagingAndSortingRepository<AssetEntity,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT a FROM AssetEntity a, RelationEntity re WHERE a.tenantId = :tenantId " +
"AND a.id = re.toId AND re.toType = 'ASSET' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"AND a.type = :type " +
"AND LOWER(a.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<AssetEntity> findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId,
@Param("edgeId") UUID edgeId,
@Param("type") String type,
@Param("searchText") String searchText,
Pageable pageable);
Long countByTenantIdAndTypeIsNot(UUID tenantId, String type);
}

View File

@ -199,6 +199,18 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao<AssetEntity, Asset> im
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, TimePageLink pageLink) {
log.debug("Try to find assets by tenantId [{}], edgeId [{}], type [{}] and pageLink [{}]", tenantId, edgeId, type, pageLink);
return DaoUtil.toPageData(assetRepository
.findByTenantIdAndEdgeIdAndType(
tenantId,
edgeId,
type,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
@Override
public Long countByTenantId(TenantId tenantId) {
return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE);

View File

@ -20,7 +20,6 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.AssetEntity;
import org.thingsboard.server.dao.model.sql.DeviceEntity;
import org.thingsboard.server.dao.model.sql.DeviceInfoEntity;

View File

@ -129,4 +129,15 @@ public interface EntityViewRepository extends PagingAndSortingRepository<EntityV
@Param("edgeId") UUID edgeId,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT ev FROM EntityViewEntity ev, RelationEntity re WHERE ev.tenantId = :tenantId " +
"AND ev.id = re.toId AND re.toType = 'ENTITY_VIEW' AND re.relationTypeGroup = 'EDGE' " +
"AND re.relationType = 'Contains' AND re.fromId = :edgeId AND re.fromType = 'EDGE' " +
"AND ev.type = :type " +
"AND LOWER(ev.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<EntityViewEntity> findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId,
@Param("edgeId") UUID edgeId,
@Param("type") String type,
@Param("searchText") String searchText,
Pageable pageable);
}

View File

@ -192,4 +192,16 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao<EntityViewEntity,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
@Override
public PageData<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, PageLink pageLink) {
log.debug("Try to find entity views by tenantId [{}], edgeId [{}], type [{}] and pageLink [{}]", tenantId, edgeId, type, pageLink);
return DaoUtil.toPageData(entityViewRepository
.findByTenantIdAndEdgeIdAndType(
tenantId,
edgeId,
type,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}
}

View File

@ -99,7 +99,7 @@ export class AssetService {
return this.http.delete(`/api/edge/${edgeId}/asset/${assetId}`, defaultHttpOptionsFromConfig(config));
}
public getEdgeAssets(edgeId, pageLink: PageLink, type: string = '',
public getEdgeAssets(edgeId: string, pageLink: PageLink, type: string = '',
config?: RequestConfig): Observable<PageData<AssetInfo>> {
return this.http.get<PageData<AssetInfo>>(`/api/edge/${edgeId}/assets${pageLink.toQuery()}&type=${type}`,
defaultHttpOptionsFromConfig(config));

View File

@ -76,12 +76,6 @@ export class EdgeService {
defaultHttpOptionsFromConfig(config));
}
public setRootRuleChain(edgeId: string, ruleChainId: string,
config?: RequestConfig): Observable<Edge> {
return this.http.post<Edge>(`/api/edge/${edgeId}/${ruleChainId}/root`,
defaultHttpOptionsFromConfig(config));
}
public getTenantEdgeInfos(pageLink: PageLink, type: string = '',
config?: RequestConfig): Observable<PageData<EdgeInfo>> {
return this.http.get<PageData<EdgeInfo>>(`/api/tenant/edgeInfos${pageLink.toQuery()}&type=${type}`,

View File

@ -44,6 +44,7 @@ import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, snakeCase } from '@core/utils';
import { DebugRuleNodeEventBody } from '@app/shared/models/event.models';
import { Edge } from "@shared/models/edge.models";
@Injectable({
providedIn: 'root'
@ -323,4 +324,9 @@ export class RuleChainService {
return this.http.get<Array<RuleChain>>(`/api/ruleChain/defaultEdgeRuleChains`, defaultHttpOptionsFromConfig(config));
}
public setEdgeRootRuleChain(edgeId: string, ruleChainId: string, config?: RequestConfig): Observable<Edge> { //TODO deaflynx EdgeInfo vs. Edge check usage
return this.http.post<Edge>(`/api/edge/${edgeId}/${ruleChainId}/root`,
defaultHttpOptionsFromConfig(config));
}
}

View File

@ -101,6 +101,17 @@ export class EntityFilterViewComponent implements ControlValueAccessor {
{deviceType});
}
break;
case AliasFilterType.edgeType:
const edgeType = this.filter.edgeType;
prefix = this.filter.edgeNameFilter;
if (prefix && prefix.length) {
this.filterDisplayValue = this.translate.instant('alias.filter-type-edge-type-and-name-description',
{edgeType, prefix});
} else {
this.filterDisplayValue = this.translate.instant('alias.filter-type-edge-type-description',
{edgeType});
}
break;
case AliasFilterType.entityViewType:
const entityView = this.filter.entityViewType;
prefix = this.filter.entityViewNameFilter;
@ -166,6 +177,7 @@ export class EntityFilterViewComponent implements ControlValueAccessor {
break;
case AliasFilterType.assetSearchQuery:
case AliasFilterType.deviceSearchQuery:
case AliasFilterType.edgeSearchQuery:
case AliasFilterType.entityViewSearchQuery:
allEntitiesText = this.translate.instant('alias.all-entities');
anyRelationText = this.translate.instant('alias.any-relation');
@ -207,6 +219,16 @@ export class EntityFilterViewComponent implements ControlValueAccessor {
this.filterDisplayValue = this.translate.instant('alias.filter-type-device-search-query-description',
translationValues
);
} else if (this.filter.type === AliasFilterType.edgeSearchQuery) {
const edgeTypesQuoted = [];
this.filter.edgeTypes.forEach((filterEdgeType) => {
edgeTypesQuoted.push(`'${filterEdgeType}'`);
});
const edgeTypesText = edgeTypesQuoted.join(', ');
translationValues.edgeTypes = edgeTypesText;
this.filterDisplayValue = this.translate.instant('alias.filter-type-edge-search-query-description',
translationValues
);
} else if (this.filter.type === AliasFilterType.entityViewSearchQuery) {
const entityViewTypesQuoted = [];
this.filter.entityViewTypes.forEach((filterEntityViewType) => {

View File

@ -103,6 +103,16 @@
<input matInput formControlName="entityViewNameFilter">
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="aliasFilterType.edgeType">
<tb-entity-subtype-autocomplete required
formControlName="edgeType"
[entityType]="entityType.EDGE">
</tb-entity-subtype-autocomplete>
<mat-form-field class="mat-block">
<mat-label translate>edge.name-starts-with</mat-label>
<input matInput formControlName="edgeNameFilter">
</mat-form-field>
</ng-template>
<ng-template [ngSwitchCase]="aliasFilterType.relationsQuery">
<section fxLayout="column" id="relationsQueryFilter">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
@ -169,6 +179,7 @@
</ng-template>
<ng-template [ngSwitchCase]="entityFilterFormGroup.get('type').value === aliasFilterType.assetSearchQuery ||
entityFilterFormGroup.get('type').value === aliasFilterType.deviceSearchQuery ||
entityFilterFormGroup.get('type').value === aliasFilterType.edgeSearchQuery ||
entityFilterFormGroup.get('type').value === aliasFilterType.entityViewSearchQuery ?
entityFilterFormGroup.get('type').value : ''">
<label class="tb-small">{{ 'alias.root-entity' | translate }}</label>
@ -248,6 +259,14 @@
formControlName="deviceTypes">
</tb-entity-subtype-list>
</ng-template>
<ng-template [ngSwitchCase]="aliasFilterType.edgeSearchQuery">
<div class="mat-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>edge.edge-types</div>
<tb-entity-subtype-list
required
[entityType]="entityType.EDGE"
formControlName="edgeTypes">
</tb-entity-subtype-list>
</ng-template>
<ng-template [ngSwitchCase]="aliasFilterType.entityViewSearchQuery">
<div class="mat-caption tb-required" style="color: rgba(0,0,0,0.57);" translate>entity-view.entity-view-types</div>
<tb-entity-subtype-list

View File

@ -141,6 +141,12 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit {
deviceNameFilter: [filter ? filter.deviceNameFilter : '', []],
});
break;
case AliasFilterType.edgeType:
this.filterFormGroup = this.fb.group({
edgeType: [filter ? filter.edgeType : null, [Validators.required]],
edgeNameFilter: [filter ? filter.edgeNameFilter : '', []],
});
break;
case AliasFilterType.entityViewType:
this.filterFormGroup = this.fb.group({
entityViewType: [filter ? filter.entityViewType : null, [Validators.required]],
@ -153,6 +159,7 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit {
case AliasFilterType.relationsQuery:
case AliasFilterType.assetSearchQuery:
case AliasFilterType.deviceSearchQuery:
case AliasFilterType.edgeSearchQuery:
case AliasFilterType.entityViewSearchQuery:
this.filterFormGroup = this.fb.group({
rootStateEntity: [filter ? filter.rootStateEntity : false, []],
@ -179,6 +186,9 @@ export class EntityFilterComponent implements ControlValueAccessor, OnInit {
} else if (type === AliasFilterType.deviceSearchQuery) {
this.filterFormGroup.addControl('deviceTypes',
this.fb.control(filter ? filter.deviceTypes : [], [Validators.required]));
} else if (type === AliasFilterType.edgeSearchQuery) {
this.filterFormGroup.addControl('edgeTypes',
this.fb.control(filter ? filter.edgeTypes : [], [Validators.required]));
} else if (type === AliasFilterType.entityViewSearchQuery) {
this.filterFormGroup.addControl('entityViewTypes',
this.fb.control(filter ? filter.entityViewTypes : [], [Validators.required]));

View File

@ -190,9 +190,9 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
this.columns.push(
new EntityTableColumn<Event>('type', 'event.type', '100%',
(entity) => entity.type, entity => ({}), false),
new EntityTableColumn<Event>('action', 'event.action', '100%',
new EntityTableColumn<Event>('action', 'edge.event-action', '100%',
(entity) => entity.action, entity => ({}), false),
new EntityTableColumn<Event>('entityId', 'event.entityId', '100%',
new EntityTableColumn<Event>('entityId', 'edge.entity-id', '100%',
(entity) => entity.id.id, entity => ({}), false), //TODO: replace this to entity.entityId because of conflict wiht entityId model
new EntityTableColumn<Event>('status', 'event.status', '100%',
(entity) => this.updateEdgeEventStatus(entity.createdTime),

View File

@ -34,6 +34,12 @@
[fxShow]="!isEdit && (assetScope === 'customer' || assetScope === 'tenant') && isAssignedToCustomer(entity)">
{{ (entity?.customerIsPublic ? 'asset.make-private' : 'asset.unassign-from-customer') | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'unassignFromEdge')"
[fxShow]="!isEdit && assetScope === 'edge'">
{{ 'edge.unassign-from-edge' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"

View File

@ -466,6 +466,9 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
case 'unassignFromCustomer':
this.unassignFromCustomer(action.event, action.entity);
return true;
case 'unassignFromEdge':
this.unassignFromEdge(action.event, action.entity);
return true;
}
return false;
}

View File

@ -53,6 +53,12 @@
[fxShow]="!isEdit && dashboardScope === 'customer' && !isCurrentPublicCustomer(entity)">
{{ 'dashboard.unassign-from-customer' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'unassignFromEdge')"
[fxShow]="!isEdit && dashboardScope === 'edge'">
{{ 'edge.unassign-from-edge' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"

View File

@ -249,6 +249,12 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}
if (dashboardScope === 'edge') {
actions.push(
{
name: this.translate.instant('dashboard.export'),
icon: 'file_download',
isEnabled: () => true,
onAction: ($event, entity) => this.exportDashboard($event, entity)
},
{
name: this.translate.instant('edge.unassign-from-edge'),
icon: 'portable_wifi_off',
@ -351,7 +357,10 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}
if (this.config.componentsData.dashboardScope === 'customer') {
this.router.navigateByUrl(`customers/${this.config.componentsData.customerId}/dashboards/${dashboard.id.id}`);
} else {
} else if (this.config.componentsData.dashboardScope === 'edge') {
this.router.navigateByUrl(`edges/${this.config.componentsData.edgeId}/dashboards/${dashboard.id.id}`);
}
else {
this.router.navigateByUrl(`dashboards/${dashboard.id.id}`);
}
}
@ -543,6 +552,9 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
case 'unassignFromCustomer':
this.unassignFromCustomer(action.event, action.entity, this.config.componentsData.customerId);
return true;
case 'unassignFromEdge':
this.unassignFromEdge(action.event, action.entity);
return true;
}
return false;
}
@ -572,7 +584,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
$event.stopPropagation();
}
this.dialogService.confirm(
this.translate.instant('dashboard.unassign-dashboard-from-edge-title', {dashboardName: dashboard.name}),
this.translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title}),
this.translate.instant('dashboard.unassign-dashboard-from-edge-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),

View File

@ -40,6 +40,12 @@
[fxShow]="!isEdit">
{{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'unassignFromEdge')"
[fxShow]="!isEdit && deviceScope === 'edge'">
{{ 'edge.unassign-from-edge' | translate }}
</button>
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"

View File

@ -544,6 +544,9 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
case 'unassignFromCustomer':
this.unassignFromCustomer(action.event, action.entity);
return true;
case 'unassignFromEdge':
this.unassignFromEdge(action.event, action.entity);
return true;
case 'manageCredentials':
this.manageCredentials(action.event, action.entity);
return true;

View File

@ -24,6 +24,9 @@ import { DevicesTableConfigResolver } from "@home/pages/device/devices-table-con
import { EntityViewsTableConfigResolver } from "@home/pages/entity-view/entity-views-table-config.resolver";
import { DashboardsTableConfigResolver } from "@home/pages/dashboard/dashboards-table-config.resolver";
import { RuleChainsTableConfigResolver } from "@home/pages/rulechain/rulechains-table-config.resolver";
import { DashboardPageComponent } from "@home/pages/dashboard/dashboard-page.component";
import { dashboardBreadcumbLabelFunction, DashboardResolver } from "@home/pages/dashboard/dashboard-routing.module";
import { BreadCrumbConfig } from "@shared/components/breadcrumb";
const routes: Routes = [
{
@ -53,7 +56,7 @@ const routes: Routes = [
auth: [Authority.TENANT_ADMIN],
ruleChainsType: 'edge',
breadcrumb: {
label: 'edge.rulechains',
label: 'rulechain.edge-rulechains',
icon: 'settings_ethernet'
},
},
@ -108,19 +111,43 @@ const routes: Routes = [
},
{
path: ':edgeId/dashboards',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
dashboardsType: 'edge',
breadcrumb: {
label: 'edge.dashboards',
icon: 'dashboard'
}
},
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
dashboardsType: 'edge'
},
resolve: {
entitiesTableConfig: DashboardsTableConfigResolver
},
},
{
path: ':dashboardId',
component: DashboardPageComponent,
data: {
breadcrumb: {
labelFunction: dashboardBreadcumbLabelFunction,
icon: 'dashboard'
} as BreadCrumbConfig<DashboardPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.dashboard',
widgetEditMode: false
},
resolve: {
dashboard: DashboardResolver
}
}]
}
]
},
]
}]
@NgModule({

View File

@ -103,7 +103,8 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<EdgeInfo>> {
const routeParams = route.params;
this.config.componentsData = {
edgeScope: route.data.edgesType
edgeScope: route.data.edgesType,
edgeType: ''
};
this.customerId = routeParams.customerId;
return this.store.pipe(select(selectAuthUser), take(1)).pipe(
@ -152,7 +153,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
new EntityTableColumn<EdgeInfo>('customerIsPublic', 'edge.public', '60px',
entity => {
return checkBoxCell(entity.customerIsPublic);
}, () => ({}), false),
}, () => ({}), false)
);
}
return columns;
@ -161,7 +162,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
configureEntityFunctions(edgeScope: string): void {
if (edgeScope === 'tenant') {
this.config.entitiesFetchFunction = pageLink =>
this.edgeService.getTenantEdgeInfos(pageLink);
this.edgeService.getTenantEdgeInfos(pageLink, this.config.componentsData.edgeType);
this.config.deleteEntity = id => this.edgeService.deleteEdge(id.id);
}
if (edgeScope === 'customer') {

View File

@ -34,6 +34,12 @@
[fxShow]="!isEdit && (entityViewScope === 'customer' || entityViewScope === 'tenant') && isAssignedToCustomer(entity)">
{{ (entity?.customerIsPublic ? 'entity-view.make-private' : 'entity-view.unassign-from-customer') | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'unassignFromEdge')"
[fxShow]="!isEdit && entityViewScope === 'edge'">
{{ 'edge.unassign-from-edge' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"

View File

@ -441,6 +441,9 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
case 'unassignFromCustomer':
this.unassignFromCustomer(action.event, action.entity);
return true;
case 'unassignFromEdge':
this.unassignFromEdge(action.event, action.entity);
return true;
}
return false;
}

View File

@ -210,7 +210,7 @@ const routes: Routes = [
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'edge.rulechains',
title: 'rulechain.edge-rulechains',
ruleChainsType: 'edges'
},
resolve: {
@ -227,7 +227,7 @@ const routes: Routes = [
icon: 'settings_ethernet'
} as BreadCrumbConfig<RuleChainPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'edge.rulechain',
title: 'rulechain.edge-rulechain',
import: false,
ruleChainType: ruleChainType.edge
},
@ -248,7 +248,7 @@ const routes: Routes = [
icon: 'settings_ethernet'
} as BreadCrumbConfig<RuleChainPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'edge.rulechain',
title: 'rulechain.edge-rulechain',
import: true,
ruleChainType: ruleChainType.edge
},

View File

@ -31,7 +31,19 @@
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setRoot')"
[fxShow]="!isEdit && !entity?.root">
[fxShow]="!isEdit && !entity?.root && ruleChainScope === 'tenant'">
{{'rulechain.set-root' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefaultRoot')"
[fxShow]="!isEdit && !entity?.root && ruleChainScope === 'edges'">
{{'rulechain.set-default-root-edge' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setRoot')"
[fxShow]="!isEdit && !isRootRuleChain() && ruleChainScope === 'edge'">
{{'rulechain.set-root' | translate }}
</button>
<button mat-raised-button color="primary"

View File

@ -31,6 +31,8 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
})
export class RuleChainComponent extends EntityComponent<RuleChain> {
ruleChainScope: 'tenant' | 'edges' | 'edge';
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: RuleChain,
@ -39,6 +41,11 @@ export class RuleChainComponent extends EntityComponent<RuleChain> {
super(store, fb, entityValue, entitiesTableConfigValue);
}
ngOnInit() {
this.ruleChainScope = this.entitiesTableConfig.componentsData.ruleChainScope;
super.ngOnInit();
}
hideDelete() {
if (this.entitiesTableConfig) {
return !this.entitiesTableConfig.deleteEnabled(this.entity);
@ -78,4 +85,12 @@ export class RuleChainComponent extends EntityComponent<RuleChain> {
horizontalPosition: 'right'
}));
}
isRootRuleChain() {
if (this.entitiesTableConfig && this.entityValue) {
return this.entitiesTableConfig.componentsData.rootRuleChainId == this.entityValue.id.id;
} else {
return false;
}
}
}

View File

@ -20,7 +20,7 @@ import {ActivatedRouteSnapshot, Resolve, Route, Router} from '@angular/router';
import {
CellActionDescriptor,
checkBoxCell,
DateEntityTableColumn,
DateEntityTableColumn, EntityColumn,
EntityTableColumn,
EntityTableConfig,
GroupActionDescriptor, HeaderActionDescriptor
@ -37,7 +37,7 @@ import { RuleChainTabsComponent } from '@home/pages/rulechain/rulechain-tabs.com
import { ImportExportService } from '@home/components/import-export/import-export.service';
import { ItemBufferService } from '@core/services/item-buffer.service';
import { EdgeService } from "@core/http/edge.service";
import { map } from "rxjs/operators";
import {map, mergeMap} from "rxjs/operators";
import { forkJoin, Observable } from "rxjs";
import {
AddEntitiesToEdgeDialogComponent,
@ -69,18 +69,6 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.entityTranslations = entityTypeTranslations.get(EntityType.RULE_CHAIN);
this.config.entityResources = entityTypeResources.get(EntityType.RULE_CHAIN);
this.config.columns.push(
new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%'),
new EntityTableColumn<RuleChain>('root', 'rulechain.root', '60px',
entity => {
if (isDefined(this.config.componentsData.edgeId)) {
return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id == entity.id.id));
} else {
return checkBoxCell(entity.root);
}
})
);
this.config.deleteEntityTitle = ruleChain => this.translate.instant('rulechain.delete-rulechain-title',
{ ruleChainName: ruleChain.name });
this.config.deleteEntityContent = () => this.translate.instant('rulechain.delete-rulechain-text');
@ -98,25 +86,56 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
ruleChainScope: route.data.ruleChainsType,
edgeId: routeParams.edgeId
};
if (this.config.componentsData.edgeId) {
this.config.entitySelectionEnabled = ruleChain => this.config.componentsData.edge.rootRuleChainId.id != ruleChain.id.id;
this.config.deleteEnabled = () => false;
this.edgeService.getEdge(this.config.componentsData.edgeId).subscribe(edge => {
this.config.componentsData.edge = edge;
this.config.tableTitle = edge.name + ': ' + this.translate.instant('rulechain.edge-rulechains');
});
}
else {
this.config.entitySelectionEnabled = ruleChain => ruleChain && !ruleChain.root;
this.config.deleteEnabled = (ruleChain) => ruleChain && !ruleChain.root;
}
this.config.columns = this.configureEntityTableColumns(this.config.componentsData.ruleChainScope);
this.configureEntityFunctions(this.config.componentsData.ruleChainScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.ruleChainScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.ruleChainScope);
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.ruleChainScope);
if (this.config.componentsData.ruleChainScope === 'tenant' || this.config.componentsData.ruleChainScope === 'edges') {
this.config.entitySelectionEnabled = ruleChain => ruleChain && !ruleChain.root;
this.config.deleteEnabled = (ruleChain) => ruleChain && !ruleChain.root;
this.config.entitiesDeleteEnabled = true;
}
else if (this.config.componentsData.ruleChainScope === 'edge') {
this.config.entitySelectionEnabled = ruleChain => this.config.componentsData.edge.rootRuleChainId.id != ruleChain.id.id;
this.edgeService.getEdge(this.config.componentsData.edgeId).subscribe(edge => {
this.config.componentsData.edge = edge;
this.config.tableTitle = edge.name + ': ' + this.translate.instant('rulechain.edge-rulechains');
});
this.config.entitiesDeleteEnabled = false;
}
return this.config;
}
configureEntityTableColumns(ruleChainScope: string): Array<EntityColumn<RuleChain>> {
const columns: Array<EntityColumn<RuleChain>> = [];
if (ruleChainScope === 'tenant' || ruleChainScope === 'edge') {
columns.push(
new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%'),
new EntityTableColumn<RuleChain>('root', 'rulechain.root', '60px',
entity => {
if (ruleChainScope === 'edge') {
return checkBoxCell((this.config.componentsData.edge.rootRuleChainId.id == entity.id.id));
} else {
return checkBoxCell(entity.root);
}
})
);
}
if (ruleChainScope === 'edges') {
columns.push(
new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%'),
new EntityTableColumn<RuleChain>('root', 'rulechain.default-root', '60px',
entity => {
return checkBoxCell(entity.root);
})
);
}
return columns;
}
configureAddActions(ruleChainScope: string): Array<HeaderActionDescriptor> {
const actions: Array<HeaderActionDescriptor> = [];
if (ruleChainScope === 'tenant' || ruleChainScope === 'edges') {
@ -293,8 +312,8 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
true
).subscribe((res) => {
if (res) {
if (this.config.componentsData.edgeId) {
this.edgeService.setRootRuleChain(this.config.componentsData.edgeId, ruleChain.id.id).subscribe(
if (this.config.componentsData.ruleChainScope === 'edge') {
this.ruleChainService.setEdgeRootRuleChain(this.config.componentsData.edgeId, ruleChain.id.id).subscribe(
(edge) => {
this.config.componentsData.edge = edge;
this.config.table.updateData();
@ -323,6 +342,9 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
case 'setRoot':
this.setRootRuleChain(action.event, action.entity);
return true;
case 'setDefaultRoot':
this.setDefaultRootEdgeRuleChain(action.event, action.entity);
return true;
}
return false;
}
@ -465,18 +487,18 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
}
isNonRootRuleChain(ruleChain: RuleChain) {
if (this.config.componentsData.edgeId) {
if (this.config.componentsData.ruleChainScope === 'edge') {
return (isDefined(this.config.componentsData.edge.rootRuleChainId) && this.config.componentsData.edge.rootRuleChainId != null && this.config.componentsData.edge.rootRuleChainId.id != ruleChain.id.id);
}
return (isDefined(ruleChain)) && !ruleChain.root;
}
isDefaultEdgeRuleChain(ruleChain) {
return (isDefined(ruleChain)) && !ruleChain.root && ruleChain.isDefault;
return (isDefined(ruleChain)) && !ruleChain.root && this.config.componentsData.defaultEdgeRuleChainIds.includes(ruleChain.id.id);
}
isNonDefaultEdgeRuleChain(ruleChain) {
return (isDefined(ruleChain)) && !ruleChain.root && !ruleChain.isDefault;
return (isDefined(ruleChain)) && !ruleChain.root && !this.config.componentsData.defaultEdgeRuleChainIds.includes(ruleChain.id.id);
}
fetchRuleChains(pageLink: PageLink) {
@ -484,17 +506,10 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
}
fetchEdgeRuleChains(pageLink: PageLink) {
let defaultEdgeRuleChainIds: Array<string> = [];
this.config.componentsData.defaultEdgeRuleChainIds = [];
this.ruleChainService.getDefaultEdgeRuleChains().subscribe(ruleChains => {
ruleChains.map(ruleChain => defaultEdgeRuleChainIds.push(ruleChain.id.id))
ruleChains.map(ruleChain => this.config.componentsData.defaultEdgeRuleChainIds.push(ruleChain.id.id));
});
return this.ruleChainService.getRuleChains(pageLink, ruleChainType.edge).pipe(
map(response => {
response.data.map(ruleChain =>
ruleChain.isDefault = defaultEdgeRuleChainIds.some(id => ruleChain.id.id.includes(id))
);
return response;
})
);
return this.ruleChainService.getRuleChains(pageLink, ruleChainType.edge);
}
}

View File

@ -143,9 +143,10 @@ export class EntitySubTypeListComponent implements ControlValueAccessor, OnInit,
break;
case EntityType.EDGE:
this.placeholder = this.required ? this.translate.instant('edge.enter-edge-type')
: this.translate.instant('edge.edge-any-edge');
: this.translate.instant('edge.any-edge');
this.secondaryPlaceholder = '+' + this.translate.instant('edge.edge-type');
this.noSubtypesMathingText = 'edge.no-edge-types-matching';
this.subtypeListEmptyText = 'edge.edge-type-list-empty';
this.broadcastSubscription = this.broadcast.on('edgeSaved', () => {
this.entitySubtypes = null;
});

View File

@ -33,3 +33,4 @@ export * from './tenant-profile-id';
export * from './user-id';
export * from './widget-type-id';
export * from './widgets-bundle-id';
export * from './edge-id';

View File

@ -1179,8 +1179,6 @@
"unassign-from-edge": "Unassign from edge",
"dashboards": "Edge Dashboards",
"manage-edge-rulechains": "Manage edge rule chains",
"rulechains": "Edge Rule Chains",
"rulechain": "Edge Rule Chain",
"edge-key": "Edge key",
"copy-edge-key": "Copy Edge key",
"edge-key-copied-message": "Edge key has been copied to clipboard",
@ -1204,6 +1202,9 @@
"enter-edge-type": "Enter entity view type",
"any-edge": "Any edge",
"no-edge-types-matching": "No edge types matching '{{entitySubtype}}' were found.",
"edge-type-list-empty": "No device types selected.",
"edge-types": "Edge types",
"dashboard": "Edge dashboard",
"unassign-edges-action-title": "Unassign { count, plural, 1 {1 edge} other {# edges} } from customer"
},
"error": {
@ -2035,6 +2036,7 @@
"open-rulechain": "Open rule chain",
"assign-new-rulechain": "Assign new rulechain",
"edge-rulechains": "Edge Rule chains",
"edge-rulechain": "Edge Rule chain",
"core-rulechains": "Core Rule chains",
"unassign-rulechain-from-edge-text": "After the confirmation the rulechain will be unassigned and won't be accessible by the edge.",
"unassign-rulechains-from-edge-title": "Unassign { count, plural, 1 {1 rulechain} other {# rulechains} } from edge",
@ -2054,7 +2056,8 @@
"unassign-rulechain-title": "Are you sure you want to unassign the rulechain '{{ruleChainName}}'?",
"unassign-rulechains-title": "Are you sure you want to unassign { count, plural, 1 {1 rulechain} other {# rulechains} }?",
"unassign-rulechains": "Unassign rulechains",
"default": "Default"
"default": "Default",
"default-root": "Default root"
},
"rulenode": {
"details": "Details",

View File

@ -194,7 +194,7 @@ export default function EdgeRoutes($stateProvider, types) {
},
data: {
searchEnabled: true,
pageTitle: 'edge.rulechains',
pageTitle: 'rulechain.edge-rulechains',
ruleChainsType: 'edge'
},
ncyBreadcrumb: {