Entity Relations Table
This commit is contained in:
parent
fea3c368a3
commit
851a3657db
113
ui-ngx/src/app/core/http/entity-relation.service.ts
Normal file
113
ui-ngx/src/app/core/http/entity-relation.service.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
///
|
||||||
|
/// 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 { EntityRelation, EntityRelationInfo, EntityRelationsQuery } from '@shared/models/relation.models';
|
||||||
|
import { EntityId } from '@app/shared/models/id/entity-id';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class EntityRelationService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public saveRelation(relation: EntityRelation, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> {
|
||||||
|
return this.http.post<EntityRelation>('/api/relation', relation, defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteRelation(fromId: EntityId, relationType: string, toId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
|
||||||
|
return this.http.delete(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
|
||||||
|
`&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteRelations(entityId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false) {
|
||||||
|
return this.http.delete(`/api/relations?entityId=${entityId.id}&entityType=${entityId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRelation(fromId: EntityId, relationType: string, toId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<EntityRelation> {
|
||||||
|
return this.http.get<EntityRelation>(`/api/relation?fromId=${fromId.id}&fromType=${fromId.entityType}` +
|
||||||
|
`&relationType=${relationType}&toId=${toId.id}&toType=${toId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByFrom(fromId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
|
||||||
|
return this.http.get<Array<EntityRelation>>(
|
||||||
|
`/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findInfoByFrom(fromId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> {
|
||||||
|
return this.http.get<Array<EntityRelationInfo>>(
|
||||||
|
`/api/relations/info?fromId=${fromId.id}&fromType=${fromId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByFromAndType(fromId: EntityId, relationType: string,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
|
||||||
|
return this.http.get<Array<EntityRelation>>(
|
||||||
|
`/api/relations?fromId=${fromId.id}&fromType=${fromId.entityType}&relationType=${relationType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByTo(toId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
|
||||||
|
return this.http.get<Array<EntityRelation>>(
|
||||||
|
`/api/relations?toId=${toId.id}&toType=${toId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findInfoByTo(toId: EntityId,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> {
|
||||||
|
return this.http.get<Array<EntityRelationInfo>>(
|
||||||
|
`/api/relations/info?toId=${toId.id}&toType=${toId.entityType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByToAndType(toId: EntityId, relationType: string,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
|
||||||
|
return this.http.get<Array<EntityRelation>>(
|
||||||
|
`/api/relations?toId=${toId.id}&toType=${toId.entityType}&relationType=${relationType}`,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findByQuery(query: EntityRelationsQuery,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelation>> {
|
||||||
|
return this.http.post<Array<EntityRelation>>(
|
||||||
|
'/api/relations', query,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
public findInfoByQuery(query: EntityRelationsQuery,
|
||||||
|
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntityRelationInfo>> {
|
||||||
|
return this.http.post<Array<EntityRelationInfo>>(
|
||||||
|
'/api/relations/info', query,
|
||||||
|
defaultHttpOptions(ignoreLoading, ignoreErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ import { AuditLogDetailsDialogComponent } from './audit-log/audit-log-details-di
|
|||||||
import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
|
import { AuditLogTableComponent } from './audit-log/audit-log-table.component';
|
||||||
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
|
import { EventTableHeaderComponent } from '@home/components/event/event-table-header.component';
|
||||||
import { EventTableComponent } from '@home/components/event/event-table.component';
|
import { EventTableComponent } from '@home/components/event/event-table.component';
|
||||||
|
import { RelationTableComponent } from '@home/components/relation/relation-table.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
@ -43,7 +44,8 @@ import { EventTableComponent } from '@home/components/event/event-table.componen
|
|||||||
AuditLogTableComponent,
|
AuditLogTableComponent,
|
||||||
AuditLogDetailsDialogComponent,
|
AuditLogDetailsDialogComponent,
|
||||||
EventTableHeaderComponent,
|
EventTableHeaderComponent,
|
||||||
EventTableComponent
|
EventTableComponent,
|
||||||
|
RelationTableComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -56,7 +58,8 @@ import { EventTableComponent } from '@home/components/event/event-table.componen
|
|||||||
EntityDetailsPanelComponent,
|
EntityDetailsPanelComponent,
|
||||||
ContactComponent,
|
ContactComponent,
|
||||||
AuditLogTableComponent,
|
AuditLogTableComponent,
|
||||||
EventTableComponent
|
EventTableComponent,
|
||||||
|
RelationTableComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class HomeComponentsModule { }
|
export class HomeComponentsModule { }
|
||||||
|
|||||||
@ -0,0 +1,168 @@
|
|||||||
|
<!--
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
-->
|
||||||
|
<div class="mat-padding tb-entity-table tb-absolute-fill">
|
||||||
|
<div fxFlex fxLayout="column" class="mat-elevation-z1 tb-entity-table-content">
|
||||||
|
<mat-toolbar class="mat-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
|
||||||
|
<div class="mat-toolbar-tools">
|
||||||
|
<span class="tb-entity-table-title">{{(direction == directions.FROM ?
|
||||||
|
'relation.from-relations' : 'relation.to-relations') | translate}}</span>
|
||||||
|
<mat-form-field class="mat-block tb-relation-direction" style="width: 200px;">
|
||||||
|
<mat-label translate>relation.direction</mat-label>
|
||||||
|
<mat-select matInput [ngModel]="direction"
|
||||||
|
(ngModelChange)="directionChanged($event)">
|
||||||
|
<mat-option *ngFor="let type of directionTypes" [value]="type">
|
||||||
|
{{ directionTypeTranslations.get(type) | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
<span fxFlex></span>
|
||||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
|
||||||
|
(click)="addRelation($event)"
|
||||||
|
matTooltip="{{ 'action.add' | translate }}"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async" (click)="reloadRelations()"
|
||||||
|
matTooltip="{{ 'action.refresh' | translate }}"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<mat-icon>refresh</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async" (click)="enterFilterMode()"
|
||||||
|
matTooltip="{{ 'action.search' | translate }}"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<mat-icon>search</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar>
|
||||||
|
<mat-toolbar class="mat-table-toolbar" [fxShow]="textSearchMode && dataSource.selection.isEmpty()">
|
||||||
|
<div class="mat-toolbar-tools">
|
||||||
|
<button mat-button mat-icon-button
|
||||||
|
matTooltip="{{ 'action.search' | translate }}"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<mat-icon>search</mat-icon>
|
||||||
|
</button>
|
||||||
|
<mat-form-field fxFlex>
|
||||||
|
<mat-label> </mat-label>
|
||||||
|
<input #searchInput matInput
|
||||||
|
[(ngModel)]="pageLink.textSearch"
|
||||||
|
placeholder="{{ 'common.enter-search' | translate }}"/>
|
||||||
|
</mat-form-field>
|
||||||
|
<button mat-button mat-icon-button (click)="exitFilterMode()"
|
||||||
|
matTooltip="{{ 'action.close' | translate }}"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<mat-icon>close</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar>
|
||||||
|
<mat-toolbar class="mat-table-toolbar" color="primary" [fxShow]="!dataSource.selection.isEmpty()">
|
||||||
|
<div class="mat-toolbar-tools">
|
||||||
|
<span>
|
||||||
|
{{ translate.get('relation.selected-relations', {count: dataSource.selection.selected.length}) | async }}
|
||||||
|
</span>
|
||||||
|
<span fxFlex></span>
|
||||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
|
||||||
|
matTooltip="{{ 'action.delete' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
(click)="deleteRelations($event)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-toolbar>
|
||||||
|
<div fxFlex class="table-container">
|
||||||
|
<mat-table [dataSource]="dataSource"
|
||||||
|
matSort [matSortActive]="pageLink.sortOrder.property" [matSortDirection]="(pageLink.sortOrder.direction + '').toLowerCase()" matSortDisableClear>
|
||||||
|
<ng-container matColumnDef="select" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<mat-checkbox (change)="$event ? dataSource.masterToggle() : null"
|
||||||
|
[checked]="dataSource.selection.hasValue() && (dataSource.isAllSelected() | async)"
|
||||||
|
[indeterminate]="dataSource.selection.hasValue() && !(dataSource.isAllSelected() | async)">
|
||||||
|
</mat-checkbox>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation">
|
||||||
|
<mat-checkbox (click)="$event.stopPropagation()"
|
||||||
|
(change)="$event ? dataSource.selection.toggle(relation) : null"
|
||||||
|
[checked]="dataSource.selection.isSelected(relation)">
|
||||||
|
</mat-checkbox>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="type">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'relation.type' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation">
|
||||||
|
{{ relation.type }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="direction === directions.FROM" matColumnDef="toEntityTypeName">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'relation.to-entity-type' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation">
|
||||||
|
{{ relation.toEntityTypeName }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="direction === directions.FROM" matColumnDef="toName">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'relation.to-entity-name' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation">
|
||||||
|
{{ relation.toName }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="direction === directions.TO" matColumnDef="fromEntityTypeName">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'relation.from-entity-type' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation">
|
||||||
|
{{ relation.fromEntityTypeName }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container *ngIf="direction === directions.TO" matColumnDef="fromName">
|
||||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'relation.from-entity-name' | translate }} </mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation">
|
||||||
|
{{ relation.fromName }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="actions" stickyEnd>
|
||||||
|
<mat-header-cell *matHeaderCellDef [ngStyle]="{ minWidth: '80px', maxWidth: '80px' }">
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let relation" [ngStyle]="{ minWidth: '80px', maxWidth: '80px' }">
|
||||||
|
<div fxFlex fxLayout="row" fxLayoutAlign="end">
|
||||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
|
||||||
|
matTooltip="{{ 'relation.edit' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
(click)="editRelation($event, relation)">
|
||||||
|
<mat-icon>edit</mat-icon>
|
||||||
|
</button>
|
||||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async"
|
||||||
|
matTooltip="{{ 'relation.delete' | translate }}"
|
||||||
|
matTooltipPosition="above"
|
||||||
|
(click)="deleteRelation($event, relation)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<mat-header-row [ngClass]="{'mat-row-select': true}" *matHeaderRowDef="displayedColumns; sticky: true"></mat-header-row>
|
||||||
|
<mat-row [ngClass]="{'mat-row-select': true,
|
||||||
|
'mat-selected': dataSource.selection.isSelected(relation)}"
|
||||||
|
*matRowDef="let relation; columns: displayedColumns;" (click)="dataSource.selection.toggle(relation)"></mat-row>
|
||||||
|
</mat-table>
|
||||||
|
<span [fxShow]="dataSource.isEmpty() | async"
|
||||||
|
fxLayoutAlign="center center"
|
||||||
|
class="no-data-found" translate>{{ 'relation.no-relations-text' }}</span>
|
||||||
|
</div>
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
<mat-paginator [length]="dataSource.total() | async"
|
||||||
|
[pageIndex]="pageLink.page"
|
||||||
|
[pageSize]="pageLink.pageSize"
|
||||||
|
[pageSizeOptions]="[10, 20, 30]"></mat-paginator>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -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.
|
||||||
|
*/
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
.tb-entity-table {
|
||||||
|
.tb-entity-table-content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.tb-entity-table-title {
|
||||||
|
padding-right: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-container {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep {
|
||||||
|
.mat-sort-header-sorted .mat-sort-header-arrow {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
mat-form-field.tb-relation-direction {
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
.mat-form-field-wrapper {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-form-field-underline {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,225 @@
|
|||||||
|
///
|
||||||
|
/// 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 { AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||||
|
import { PageComponent } from '@shared/components/page.component';
|
||||||
|
import { PageLink } from '@shared/models/page/page-link';
|
||||||
|
import { MatPaginator } from '@angular/material/paginator';
|
||||||
|
import { MatSort } from '@angular/material/sort';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AppState } from '@core/core.state';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { DialogService } from '@core/services/dialog.service';
|
||||||
|
import { EntityRelationService } from '@core/http/entity-relation.service';
|
||||||
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
||||||
|
import { fromEvent, merge } from 'rxjs';
|
||||||
|
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
|
||||||
|
import { EntityRelationInfo, EntitySearchDirection, entitySearchDirectionTranslations } from '@shared/models/relation.models';
|
||||||
|
import { EntityId } from '@shared/models/id/entity-id';
|
||||||
|
import { RelationsDatasource } from '../../models/datasource/relation-datasource';
|
||||||
|
import { DebugEventType, EventType } from '@shared/models/event.models';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'tb-relation-table',
|
||||||
|
templateUrl: './relation-table.component.html',
|
||||||
|
styleUrls: ['./relation-table.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class RelationTableComponent extends PageComponent implements AfterViewInit, OnInit {
|
||||||
|
|
||||||
|
directions = EntitySearchDirection;
|
||||||
|
|
||||||
|
directionTypes = Object.keys(EntitySearchDirection);
|
||||||
|
|
||||||
|
directionTypeTranslations = entitySearchDirectionTranslations;
|
||||||
|
|
||||||
|
displayedColumns: string[];
|
||||||
|
direction: EntitySearchDirection;
|
||||||
|
pageLink: PageLink;
|
||||||
|
textSearchMode = false;
|
||||||
|
dataSource: RelationsDatasource;
|
||||||
|
|
||||||
|
activeValue = false;
|
||||||
|
dirtyValue = false;
|
||||||
|
entityIdValue: EntityId;
|
||||||
|
|
||||||
|
viewsInited = false;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set active(active: boolean) {
|
||||||
|
if (this.activeValue !== active) {
|
||||||
|
this.activeValue = active;
|
||||||
|
if (this.activeValue && this.dirtyValue) {
|
||||||
|
this.dirtyValue = false;
|
||||||
|
if (this.viewsInited) {
|
||||||
|
this.updateData(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set entityId(entityId: EntityId) {
|
||||||
|
if (this.entityIdValue !== entityId) {
|
||||||
|
this.entityIdValue = entityId;
|
||||||
|
if (this.viewsInited) {
|
||||||
|
this.resetSortAndFilter(this.activeValue);
|
||||||
|
if (!this.activeValue) {
|
||||||
|
this.dirtyValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewChild('searchInput', {static: false}) searchInputField: ElementRef;
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator;
|
||||||
|
@ViewChild(MatSort, {static: false}) sort: MatSort;
|
||||||
|
|
||||||
|
constructor(protected store: Store<AppState>,
|
||||||
|
private entityRelationService: EntityRelationService,
|
||||||
|
public translate: TranslateService,
|
||||||
|
public dialog: MatDialog,
|
||||||
|
private dialogService: DialogService) {
|
||||||
|
super(store);
|
||||||
|
this.dirtyValue = !this.activeValue;
|
||||||
|
const sortOrder: SortOrder = { property: 'type', direction: Direction.ASC };
|
||||||
|
this.direction = EntitySearchDirection.FROM;
|
||||||
|
this.pageLink = new PageLink(10, 0, null, sortOrder);
|
||||||
|
this.dataSource = new RelationsDatasource(this.entityRelationService, this.translate);
|
||||||
|
this.updateColumns();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColumns() {
|
||||||
|
if (this.direction === EntitySearchDirection.FROM) {
|
||||||
|
this.displayedColumns = ['select', 'type', 'toEntityTypeName', 'toName', 'actions'];
|
||||||
|
} else {
|
||||||
|
this.displayedColumns = ['select', 'type', 'fromEntityTypeName', 'fromName', 'actions'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
directionChanged(direction: EntitySearchDirection) {
|
||||||
|
this.direction = direction;
|
||||||
|
this.updateColumns();
|
||||||
|
this.updateData(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
|
||||||
|
fromEvent(this.searchInputField.nativeElement, 'keyup')
|
||||||
|
.pipe(
|
||||||
|
debounceTime(150),
|
||||||
|
distinctUntilChanged(),
|
||||||
|
tap(() => {
|
||||||
|
this.paginator.pageIndex = 0;
|
||||||
|
this.updateData();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
|
||||||
|
|
||||||
|
merge(this.sort.sortChange, this.paginator.page)
|
||||||
|
.pipe(
|
||||||
|
tap(() => this.updateData())
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
|
||||||
|
this.viewsInited = true;
|
||||||
|
if (this.activeValue && this.entityIdValue) {
|
||||||
|
this.updateData(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateData(reload: boolean = false) {
|
||||||
|
this.pageLink.page = this.paginator.pageIndex;
|
||||||
|
this.pageLink.pageSize = this.paginator.pageSize;
|
||||||
|
this.pageLink.sortOrder.property = this.sort.active;
|
||||||
|
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
|
||||||
|
this.dataSource.loadRelations(this.direction, this.entityIdValue, this.pageLink, reload);
|
||||||
|
}
|
||||||
|
|
||||||
|
enterFilterMode() {
|
||||||
|
this.textSearchMode = true;
|
||||||
|
this.pageLink.textSearch = '';
|
||||||
|
setTimeout(() => {
|
||||||
|
this.searchInputField.nativeElement.focus();
|
||||||
|
this.searchInputField.nativeElement.setSelectionRange(0, 0);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
exitFilterMode() {
|
||||||
|
this.textSearchMode = false;
|
||||||
|
this.pageLink.textSearch = null;
|
||||||
|
this.paginator.pageIndex = 0;
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetSortAndFilter(update: boolean = true) {
|
||||||
|
this.direction = EntitySearchDirection.FROM;
|
||||||
|
this.updateColumns();
|
||||||
|
this.pageLink.textSearch = null;
|
||||||
|
this.paginator.pageIndex = 0;
|
||||||
|
const sortable = this.sort.sortables.get('type');
|
||||||
|
this.sort.active = sortable.id;
|
||||||
|
this.sort.direction = 'asc';
|
||||||
|
if (update) {
|
||||||
|
this.updateData(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reloadRelations() {
|
||||||
|
this.updateData(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRelation($event: Event) {
|
||||||
|
this.openRelationDialog($event);
|
||||||
|
}
|
||||||
|
|
||||||
|
editRelation($event: Event, relation: EntityRelationInfo) {
|
||||||
|
this.openRelationDialog($event, relation);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRelation($event: Event, relation: EntityRelationInfo) {
|
||||||
|
if ($event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteRelations($event: Event) {
|
||||||
|
if ($event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
}
|
||||||
|
if (this.dataSource.selection.selected.length > 0) {
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
openRelationDialog($event: Event, relation: EntityRelationInfo = null) {
|
||||||
|
if ($event) {
|
||||||
|
$event.stopPropagation();
|
||||||
|
}
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
///
|
||||||
|
/// 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 { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
|
||||||
|
import { EntityRelationInfo, EntitySearchDirection } from '@shared/models/relation.models';
|
||||||
|
import { BehaviorSubject, Observable, of, ReplaySubject } from 'rxjs';
|
||||||
|
import { emptyPageData, PageData } from '@shared/models/page/page-data';
|
||||||
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
|
import { EntityRelationService } from '@core/http/entity-relation.service';
|
||||||
|
import { PageLink } from '@shared/models/page/page-link';
|
||||||
|
import { catchError, map, publishReplay, refCount, take, tap } from 'rxjs/operators';
|
||||||
|
import { EntityId } from '@app/shared/models/id/entity-id';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { entityTypeTranslations } from '@shared/models/entity-type.models';
|
||||||
|
|
||||||
|
export class RelationsDatasource implements DataSource<EntityRelationInfo> {
|
||||||
|
|
||||||
|
private relationsSubject = new BehaviorSubject<EntityRelationInfo[]>([]);
|
||||||
|
private pageDataSubject = new BehaviorSubject<PageData<EntityRelationInfo>>(emptyPageData<EntityRelationInfo>());
|
||||||
|
|
||||||
|
public pageData$ = this.pageDataSubject.asObservable();
|
||||||
|
|
||||||
|
public selection = new SelectionModel<EntityRelationInfo>(true, []);
|
||||||
|
|
||||||
|
private allRelations: Observable<Array<EntityRelationInfo>>;
|
||||||
|
|
||||||
|
constructor(private entityRelationService: EntityRelationService,
|
||||||
|
private translate: TranslateService) {}
|
||||||
|
|
||||||
|
connect(collectionViewer: CollectionViewer): Observable<EntityRelationInfo[] | ReadonlyArray<EntityRelationInfo>> {
|
||||||
|
return this.relationsSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(collectionViewer: CollectionViewer): void {
|
||||||
|
this.relationsSubject.complete();
|
||||||
|
this.pageDataSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadRelations(direction: EntitySearchDirection, entityId: EntityId,
|
||||||
|
pageLink: PageLink, reload: boolean = false): Observable<PageData<EntityRelationInfo>> {
|
||||||
|
if (reload) {
|
||||||
|
this.allRelations = null;
|
||||||
|
}
|
||||||
|
const result = new ReplaySubject<PageData<EntityRelationInfo>>();
|
||||||
|
this.fetchRelations(direction, entityId, pageLink).pipe(
|
||||||
|
tap(() => {
|
||||||
|
this.selection.clear();
|
||||||
|
}),
|
||||||
|
catchError(() => of(emptyPageData<EntityRelationInfo>())),
|
||||||
|
).subscribe(
|
||||||
|
(pageData) => {
|
||||||
|
this.relationsSubject.next(pageData.data);
|
||||||
|
this.pageDataSubject.next(pageData);
|
||||||
|
result.next(pageData);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchRelations(direction: EntitySearchDirection, entityId: EntityId,
|
||||||
|
pageLink: PageLink): Observable<PageData<EntityRelationInfo>> {
|
||||||
|
return this.getAllRelations(direction, entityId).pipe(
|
||||||
|
map((data) => pageLink.filterData(data))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllRelations(direction: EntitySearchDirection, entityId: EntityId): Observable<Array<EntityRelationInfo>> {
|
||||||
|
if (!this.allRelations) {
|
||||||
|
let relationsObservable: Observable<Array<EntityRelationInfo>>;
|
||||||
|
switch (direction) {
|
||||||
|
case EntitySearchDirection.FROM:
|
||||||
|
relationsObservable = this.entityRelationService.findInfoByFrom(entityId);
|
||||||
|
break;
|
||||||
|
case EntitySearchDirection.TO:
|
||||||
|
relationsObservable = this.entityRelationService.findInfoByTo(entityId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.allRelations = relationsObservable.pipe(
|
||||||
|
map(relations => {
|
||||||
|
relations.forEach(relation => {
|
||||||
|
if (direction === EntitySearchDirection.FROM) {
|
||||||
|
relation.toEntityTypeName = this.translate.instant(entityTypeTranslations.get(relation.to.entityType).type);
|
||||||
|
} else {
|
||||||
|
relation.fromEntityTypeName = this.translate.instant(entityTypeTranslations.get(relation.from.entityType).type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return relations;
|
||||||
|
}),
|
||||||
|
publishReplay(1),
|
||||||
|
refCount()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.allRelations;
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllSelected(): Observable<boolean> {
|
||||||
|
const numSelected = this.selection.selected.length;
|
||||||
|
return this.relationsSubject.pipe(
|
||||||
|
map((relations) => numSelected === relations.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isEmpty(): Observable<boolean> {
|
||||||
|
return this.relationsSubject.pipe(
|
||||||
|
map((relations) => !relations.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
total(): Observable<number> {
|
||||||
|
return this.pageDataSubject.pipe(
|
||||||
|
map((pageData) => pageData.totalElements)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
masterToggle() {
|
||||||
|
this.relationsSubject.pipe(
|
||||||
|
tap((relations) => {
|
||||||
|
const numSelected = this.selection.selected.length;
|
||||||
|
if (numSelected === relations.length) {
|
||||||
|
this.selection.clear();
|
||||||
|
} else {
|
||||||
|
relations.forEach(row => {
|
||||||
|
this.selection.select(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
take(1)
|
||||||
|
).subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -20,6 +20,10 @@
|
|||||||
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
|
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
|
||||||
[entityId]="entity.id"></tb-event-table>
|
[entityId]="entity.id"></tb-event-table>
|
||||||
</mat-tab>
|
</mat-tab>
|
||||||
|
<mat-tab *ngIf="entity"
|
||||||
|
label="{{ 'relation.relations' | translate }}" #relationsTab="matTab">
|
||||||
|
<tb-relation-table [active]="relationsTab.isActive" [entityId]="entity.id"></tb-relation-table>
|
||||||
|
</mat-tab>
|
||||||
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
|
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN"
|
||||||
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
|
label="{{ 'audit-log.audit-logs' | translate }}" #auditLogsTab="matTab">
|
||||||
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
|
<tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id" detailsMode="true"></tb-audit-log-table>
|
||||||
|
|||||||
@ -14,16 +14,17 @@
|
|||||||
/// limitations under the License.
|
/// limitations under the License.
|
||||||
///
|
///
|
||||||
|
|
||||||
import { BaseData, HasId } from '@shared/models/base-data';
|
import { PageLink } from '@shared/models/page/page-link';
|
||||||
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
||||||
|
|
||||||
export interface PageData<T extends BaseData<HasId>> {
|
export interface PageData<T> {
|
||||||
data: Array<T>;
|
data: Array<T>;
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
totalElements: number;
|
totalElements: number;
|
||||||
hasNext: boolean;
|
hasNext: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function emptyPageData<T extends BaseData<HasId>>(): PageData<T> {
|
export function emptyPageData<T>(): PageData<T> {
|
||||||
return {
|
return {
|
||||||
data: [],
|
data: [],
|
||||||
totalPages: 0,
|
totalPages: 0,
|
||||||
|
|||||||
@ -15,6 +15,27 @@
|
|||||||
///
|
///
|
||||||
|
|
||||||
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
import { Direction, SortOrder } from '@shared/models/page/sort-order';
|
||||||
|
import { emptyPageData, PageData } from '@shared/models/page/page-data';
|
||||||
|
|
||||||
|
export type PageLinkSearchFunction<T> = (entity: T, textSearch: string) => boolean;
|
||||||
|
|
||||||
|
const defaultPageLinkSearchFunction: PageLinkSearchFunction<any> =
|
||||||
|
(entity: any, textSearch: string) => {
|
||||||
|
if (textSearch === null || !textSearch.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const expected = ('' + textSearch).toLowerCase();
|
||||||
|
for (const key of Object.keys(entity)) {
|
||||||
|
const val = entity[key];
|
||||||
|
if (val !== null && val !== Object(val)) {
|
||||||
|
const actual = ('' + val).toLowerCase();
|
||||||
|
if (actual.indexOf(expected) !== -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
export class PageLink {
|
export class PageLink {
|
||||||
|
|
||||||
@ -65,6 +86,25 @@ export class PageLink {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public filterData<T>(data: Array<T>,
|
||||||
|
searchFunction: PageLinkSearchFunction<T> = defaultPageLinkSearchFunction): PageData<T> {
|
||||||
|
const pageData = emptyPageData<T>();
|
||||||
|
pageData.data = [...data];
|
||||||
|
if (this.textSearch && this.textSearch.length) {
|
||||||
|
pageData.data = pageData.data.filter((entity) => searchFunction(entity, this.textSearch));
|
||||||
|
}
|
||||||
|
pageData.totalElements = pageData.data.length;
|
||||||
|
pageData.totalPages = Math.ceil(pageData.totalElements / this.pageSize);
|
||||||
|
if (this.sortOrder) {
|
||||||
|
pageData.data = pageData.data.sort(this.sort);
|
||||||
|
}
|
||||||
|
const startIndex = this.pageSize * this.page;
|
||||||
|
const endIndex = startIndex + this.pageSize;
|
||||||
|
pageData.data = pageData.data.slice(startIndex, startIndex + this.pageSize);
|
||||||
|
pageData.hasNext = pageData.totalElements > startIndex + pageData.data.length;
|
||||||
|
return pageData;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimePageLink extends PageLink {
|
export class TimePageLink extends PageLink {
|
||||||
|
|||||||
87
ui-ngx/src/app/shared/models/relation.models.ts
Normal file
87
ui-ngx/src/app/shared/models/relation.models.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
///
|
||||||
|
/// 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 { EntityId } from '@shared/models/id/entity-id';
|
||||||
|
import { EntityType } from '@shared/models/entity-type.models';
|
||||||
|
import { ActionStatus } from '@shared/models/audit-log.models';
|
||||||
|
|
||||||
|
export const CONTAINS_TYPE = 'Contains';
|
||||||
|
export const MANAGES_TYPE = 'Manages';
|
||||||
|
|
||||||
|
export const RelationTypes = [
|
||||||
|
CONTAINS_TYPE,
|
||||||
|
MANAGES_TYPE
|
||||||
|
];
|
||||||
|
|
||||||
|
export enum RelationTypeGroup {
|
||||||
|
COMMON = 'COMMON',
|
||||||
|
ALARM = 'ALARM',
|
||||||
|
DASHBOARD = 'DASHBOARD',
|
||||||
|
RULE_CHAIN = 'RULE_CHAIN',
|
||||||
|
RULE_NODE = 'RULE_NODE',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum EntitySearchDirection {
|
||||||
|
FROM = 'FROM',
|
||||||
|
TO = 'TO'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const entitySearchDirectionTranslations = new Map<EntitySearchDirection, string>(
|
||||||
|
[
|
||||||
|
[EntitySearchDirection.FROM, 'relation.search-direction.FROM'],
|
||||||
|
[EntitySearchDirection.TO, 'relation.search-direction.TO'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
export const directionTypeTranslations = new Map<EntitySearchDirection, string>(
|
||||||
|
[
|
||||||
|
[EntitySearchDirection.FROM, 'relation.direction-type.FROM'],
|
||||||
|
[EntitySearchDirection.TO, 'relation.direction-type.TO'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
export interface EntityTypeFilter {
|
||||||
|
relationType: string;
|
||||||
|
entityTypes: Array<EntityType>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RelationsSearchParameters {
|
||||||
|
rootId: string;
|
||||||
|
rootType: EntityType;
|
||||||
|
direction: EntitySearchDirection;
|
||||||
|
relationTypeGroup: RelationTypeGroup;
|
||||||
|
maxLevel: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityRelationsQuery {
|
||||||
|
parameters: RelationsSearchParameters;
|
||||||
|
filters: Array<EntityTypeFilter>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityRelation {
|
||||||
|
from: EntityId;
|
||||||
|
to: EntityId;
|
||||||
|
type: string;
|
||||||
|
typeGroup: RelationTypeGroup;
|
||||||
|
additionalInfo?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityRelationInfo extends EntityRelation {
|
||||||
|
fromName: string;
|
||||||
|
toEntityTypeName?: string;
|
||||||
|
toName: string;
|
||||||
|
fromEntityTypeName?: string;
|
||||||
|
}
|
||||||
@ -1278,7 +1278,8 @@
|
|||||||
"any-relation": "Any relation",
|
"any-relation": "Any relation",
|
||||||
"relation-filters": "Relation filters",
|
"relation-filters": "Relation filters",
|
||||||
"additional-info": "Additional info (JSON)",
|
"additional-info": "Additional info (JSON)",
|
||||||
"invalid-additional-info": "Unable to parse additional info json."
|
"invalid-additional-info": "Unable to parse additional info json.",
|
||||||
|
"no-relations-text": "No relations found"
|
||||||
},
|
},
|
||||||
"rulechain": {
|
"rulechain": {
|
||||||
"rulechain": "Rule chain",
|
"rulechain": "Rule chain",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user