committed by
GitHub
34 changed files with 1874 additions and 25 deletions
File diff suppressed because one or more lines are too long
@ -0,0 +1,92 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="persistentFormGroup" (ngSubmit)="save()" style="min-width: 480px; max-width: 600px;"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ 'widgets.persistent-table.add-title' | translate }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content class="add-dialog"> |
||||
|
<fieldset [disabled]="isLoading$ | async"> |
||||
|
<div fxLayout="row" fxLayoutGap="6px"> |
||||
|
<mat-slide-toggle fxFlex class="mat-block" formControlName="oneWayElseTwoWay"> |
||||
|
{{ rpcMessageTypeText }} |
||||
|
</mat-slide-toggle> |
||||
|
</div> |
||||
|
<div fxLayout="row wrap" fxLayout.xs="column" fxLayoutGap="6px"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.method</mat-label> |
||||
|
<input matInput formControlName="method" required> |
||||
|
<mat-error *ngIf="this.persistentFormGroup.get('method').hasError('required')"> |
||||
|
{{'widgets.persistent-table.method-error' | translate}} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="this.persistentFormGroup.get('method').hasError('pattern')"> |
||||
|
{{'widgets.persistent-table.white-space-error' | translate}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.retries</mat-label> |
||||
|
<input matInput type="number" formControlName="retries"> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div class="params-json-editor"> |
||||
|
<tb-json-object-edit formControlName="params" |
||||
|
[editorStyle]="{minHeight: '130px'}" |
||||
|
label="{{ 'widgets.persistent-table.params' | translate }}"> |
||||
|
</tb-json-object-edit> |
||||
|
</div> |
||||
|
<mat-expansion-panel class="additional-json-editor"> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title translate> |
||||
|
widgets.persistent-table.additional-info |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<tb-json-object-edit formControlName="additionalInfo" |
||||
|
[editorStyle]="{minHeight: '130px'}"> |
||||
|
</tb-json-object-edit> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()"> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</button> |
||||
|
<span fxFlex></span> |
||||
|
<div fxLayout="row" fxLayoutGap="8px"> |
||||
|
<button mat-raised-button |
||||
|
color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || persistentFormGroup.invalid || !persistentFormGroup.dirty"> |
||||
|
{{ 'widgets.persistent-table.send-request' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 ::ng-deep { |
||||
|
.add-dialog { |
||||
|
|
||||
|
.params-json-editor, |
||||
|
.additional-json-editor { |
||||
|
.tb-json-object-panel { |
||||
|
margin: 0 0 16px; |
||||
|
} |
||||
|
|
||||
|
.mat-expansion-panel-body { |
||||
|
padding-bottom: 0 !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, OnInit } from '@angular/core'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
import { RequestData } from '@shared/models/rpc.models'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-persistent-add-dialog', |
||||
|
templateUrl: './persistent-add-dialog.component.html', |
||||
|
styleUrls: ['./persistent-add-dialog.component.scss'] |
||||
|
}) |
||||
|
|
||||
|
export class PersistentAddDialogComponent extends DialogComponent<PersistentAddDialogComponent, RequestData> implements OnInit { |
||||
|
|
||||
|
public persistentFormGroup: FormGroup; |
||||
|
public rpcMessageTypeText: string; |
||||
|
|
||||
|
private requestData: RequestData = null; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
public dialogRef: MatDialogRef<PersistentAddDialogComponent, RequestData>, |
||||
|
private fb: FormBuilder, |
||||
|
private translate: TranslateService) { |
||||
|
super(store, router, dialogRef); |
||||
|
|
||||
|
this.persistentFormGroup = this.fb.group( |
||||
|
{ |
||||
|
method: ['', [Validators.required, Validators.pattern(/^\S+$/)]], |
||||
|
oneWayElseTwoWay: [false], |
||||
|
retries: [null, [Validators.pattern(/^-?[0-9]+$/), Validators.min(0)]], |
||||
|
params: [null], |
||||
|
additionalInfo: [null] |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
save() { |
||||
|
this.requestData = this.persistentFormGroup.value; |
||||
|
this.close(); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.rpcMessageTypeText = this.translate.instant('widgets.persistent-table.message-types.false'); |
||||
|
this.persistentFormGroup.get('oneWayElseTwoWay').valueChanges.subscribe( |
||||
|
() => { |
||||
|
this.rpcMessageTypeText = this.translate.instant(`widgets.persistent-table.message-types.${this.persistentFormGroup.get('oneWayElseTwoWay').value}`); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(this.requestData); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,118 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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. |
||||
|
|
||||
|
--> |
||||
|
<form [formGroup]="persistentFormGroup" style="min-width: 480px;"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ persistentFormGroup.get('rpcId').value }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="close()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content> |
||||
|
<fieldset [disabled]="isLoading$ | async"> |
||||
|
<div fxLayout="row" fxLayout.xs="column" fxLayoutGap="6px"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.created-time</mat-label> |
||||
|
<input matInput formControlName="createdTime" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.expiration-time</mat-label> |
||||
|
<input matInput formControlName="expirationTime" readonly> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div fxLayout="row wrap" fxLayout.xs="column" fxLayoutGap="6px"> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.message-type</mat-label> |
||||
|
<input matInput formControlName="messageType" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.status</mat-label> |
||||
|
<input matInput formControlName="status" readonly |
||||
|
[ngStyle]="{ fontWeight: 'bold', color: rpcStatusColorsMap.get(data.persistentRequest.status) }"> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block"> |
||||
|
<mat-label translate>widgets.persistent-table.method</mat-label> |
||||
|
<input matInput formControlName="method" readonly> |
||||
|
</mat-form-field> |
||||
|
<mat-form-field fxFlex class="mat-block" |
||||
|
*ngIf="persistentFormGroup.get('retries').value"> |
||||
|
<mat-label translate>widgets.persistent-table.retries</mat-label> |
||||
|
<input matInput formControlName="retries" readonly> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
|
||||
|
<mat-accordion class="rpc-dialog" multi> |
||||
|
<mat-expansion-panel expanded> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title> |
||||
|
{{ 'widgets.persistent-table.response' | translate }} |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<tb-json-object-view formControlName="response" autoHeight></tb-json-object-view> |
||||
|
</mat-expansion-panel> |
||||
|
<mat-expansion-panel> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title> |
||||
|
{{ 'widgets.persistent-table.params' | translate }} |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<tb-json-object-view formControlName="params" autoHeight></tb-json-object-view> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
<mat-expansion-panel> |
||||
|
<mat-expansion-panel-header> |
||||
|
<mat-panel-title> |
||||
|
{{ 'widgets.persistent-table.additional-info' | translate }} |
||||
|
</mat-panel-title> |
||||
|
</mat-expansion-panel-header> |
||||
|
<ng-template matExpansionPanelContent> |
||||
|
<tb-json-object-view |
||||
|
formControlName="additionalInfo" |
||||
|
autoHeight> |
||||
|
</tb-json-object-view> |
||||
|
</ng-template> |
||||
|
</mat-expansion-panel> |
||||
|
</mat-accordion> |
||||
|
</fieldset> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayout="row"> |
||||
|
<button mat-raised-button |
||||
|
*ngIf="allowDelete" |
||||
|
color="primary" |
||||
|
type="button" |
||||
|
(click)="deleteRpcRequest()" |
||||
|
[disabled]="(isLoading$ | async)"> |
||||
|
{{ 'widgets.persistent-table.delete' | translate }} |
||||
|
</button> |
||||
|
<span fxFlex></span> |
||||
|
<div fxLayout="row" fxLayoutGap="8px"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="close()" cdkFocusInitial> |
||||
|
{{ 'action.close' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,34 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 ::ng-deep { |
||||
|
.rpc-dialog { |
||||
|
.mat-expansion-panel-body { |
||||
|
padding-bottom: 0 !important; |
||||
|
} |
||||
|
|
||||
|
.tb-json-object-panel { |
||||
|
margin: 0 0 16px 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.tb-audit-log-response-data { |
||||
|
width: 100%; |
||||
|
min-width: 400px; |
||||
|
height: 100%; |
||||
|
min-height: 100px; |
||||
|
border: 1px solid #c0c0c0; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,126 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, ElementRef, Inject, OnInit, ViewChild } from '@angular/core'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { DatePipe } from '@angular/common'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { FormBuilder, FormGroup } from '@angular/forms'; |
||||
|
import { DeviceService } from '@core/http/device.service'; |
||||
|
import { PersistentRpc, RpcStatus, rpcStatusColors, rpcStatusTranslation } from '@shared/models/rpc.models'; |
||||
|
import { NULL_UUID } from '@shared/models/id/has-uuid'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
|
||||
|
export interface PersistentDetailsDialogData { |
||||
|
persistentRequest: PersistentRpc; |
||||
|
allowDelete: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-persistent-details-dialog', |
||||
|
templateUrl: './persistent-details-dialog.component.html', |
||||
|
styleUrls: ['./persistent-details-dialog.component.scss'] |
||||
|
}) |
||||
|
|
||||
|
export class PersistentDetailsDialogComponent extends DialogComponent<PersistentDetailsDialogComponent, boolean> implements OnInit { |
||||
|
|
||||
|
@ViewChild('responseDataEditor', {static: true}) |
||||
|
responseDataEditorElmRef: ElementRef; |
||||
|
|
||||
|
public persistentFormGroup: FormGroup; |
||||
|
public rpcStatusColorsMap = rpcStatusColors; |
||||
|
public rpcStatus = RpcStatus; |
||||
|
public allowDelete: boolean; |
||||
|
|
||||
|
private persistentUpdated = false; |
||||
|
private responseData: string; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
private datePipe: DatePipe, |
||||
|
private translate: TranslateService, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: PersistentDetailsDialogData, |
||||
|
public dialogRef: MatDialogRef<PersistentDetailsDialogComponent, boolean>, |
||||
|
private dialogService: DialogService, |
||||
|
private deviceService: DeviceService, |
||||
|
private fb: FormBuilder) { |
||||
|
super(store, router, dialogRef); |
||||
|
|
||||
|
this.allowDelete = data.allowDelete; |
||||
|
|
||||
|
this.persistentFormGroup = this.fb.group( |
||||
|
{ |
||||
|
rpcId: [''], |
||||
|
createdTime: [''], |
||||
|
expirationTime: [''], |
||||
|
messageType: [''], |
||||
|
status: [''], |
||||
|
method: [''], |
||||
|
params: [''], |
||||
|
retries: [''], |
||||
|
response: [''], |
||||
|
additionalInfo: [''] |
||||
|
} |
||||
|
); |
||||
|
this.loadPersistentFields(data.persistentRequest); |
||||
|
this.responseData = JSON.stringify(data.persistentRequest.response, null, 2); |
||||
|
} |
||||
|
|
||||
|
loadPersistentFields(request: PersistentRpc) { |
||||
|
this.persistentFormGroup.patchValue({ |
||||
|
rpcId: this.translate.instant('widgets.persistent-table.details-title') + request.id.id, |
||||
|
createdTime: this.datePipe.transform(request.createdTime, 'yyyy-MM-dd HH:mm:ss'), |
||||
|
expirationTime: this.datePipe.transform(request.expirationTime, 'yyyy-MM-dd HH:mm:ss'), |
||||
|
messageType: this.translate.instant('widgets.persistent-table.message-types.' + request.request.oneway), |
||||
|
status: this.translate.instant(rpcStatusTranslation.get(request.status)), |
||||
|
method: request.request.body.method, |
||||
|
retries: request.request.retries || null, |
||||
|
response: request.response || null, |
||||
|
params: JSON.parse(request.request.body.params) || null, |
||||
|
additionalInfo: request.additionalInfo || null |
||||
|
}, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
} |
||||
|
|
||||
|
close(): void { |
||||
|
this.dialogRef.close(this.persistentUpdated); |
||||
|
} |
||||
|
|
||||
|
deleteRpcRequest() { |
||||
|
const persistentRpc = this.data.persistentRequest; |
||||
|
if (persistentRpc && persistentRpc.id && persistentRpc.id.id !== NULL_UUID) { |
||||
|
this.dialogService.confirm( |
||||
|
this.translate.instant('widgets.persistent-table.delete-request-title'), |
||||
|
this.translate.instant('widgets.persistent-table.delete-request-text'), |
||||
|
this.translate.instant('action.no'), |
||||
|
this.translate.instant('action.yes') |
||||
|
).subscribe((res) => { |
||||
|
if (res) { |
||||
|
this.deviceService.deletePersistedRpc(persistentRpc.id.id).subscribe(() => { |
||||
|
this.persistentUpdated = true; |
||||
|
this.close(); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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. |
||||
|
|
||||
|
--> |
||||
|
<form fxLayout="column" class="mat-content mat-padding" [formGroup]="persistentFilterFormGroup" (ngSubmit)="update()"> |
||||
|
<mat-form-field fxFlex class="mat-block" floatLabel="always"> |
||||
|
<mat-label translate>widgets.persistent-table.rpc-status-list</mat-label> |
||||
|
<mat-select formControlName="rpcStatus" |
||||
|
placeholder="{{ rpcSearchPlaceholder }}"> |
||||
|
<mat-option [value]="null"> |
||||
|
{{ 'widgets.persistent-table.rpc-search-status-all' | translate }} |
||||
|
</mat-option> |
||||
|
<mat-option *ngFor="let searchStatus of persistentSearchStatuses" [value]="searchStatus"> |
||||
|
{{ rpcSearchStatusTranslationMap.get(searchStatus) | translate }} |
||||
|
</mat-option> |
||||
|
</mat-select> |
||||
|
</mat-form-field> |
||||
|
<div fxLayout="row" class="tb-panel-actions" fxLayoutAlign="end center"> |
||||
|
<button type="button" |
||||
|
mat-button |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button type="submit" |
||||
|
mat-raised-button |
||||
|
color="primary" |
||||
|
[disabled]="persistentFilterFormGroup.invalid || !persistentFilterFormGroup.dirty"> |
||||
|
{{ 'action.update' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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%; |
||||
|
min-width: 300px; |
||||
|
overflow: hidden; |
||||
|
background: #fff; |
||||
|
border-radius: 4px; |
||||
|
box-shadow: |
||||
|
0 7px 8px -4px rgba(0, 0, 0, .2), |
||||
|
0 13px 19px 2px rgba(0, 0, 0, .14), |
||||
|
0 5px 24px 4px rgba(0, 0, 0, .12); |
||||
|
|
||||
|
.mat-content { |
||||
|
overflow: hidden; |
||||
|
background-color: #fff; |
||||
|
} |
||||
|
|
||||
|
.mat-padding { |
||||
|
padding: 16px; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, InjectionToken } from '@angular/core'; |
||||
|
import { FormBuilder, FormGroup } from '@angular/forms'; |
||||
|
import { OverlayRef } from '@angular/cdk/overlay'; |
||||
|
import { RpcStatus, rpcStatusTranslation } from '@shared/models/rpc.models'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
|
||||
|
export const PERSISTENT_FILTER_PANEL_DATA = new InjectionToken<any>('AlarmFilterPanelData'); |
||||
|
|
||||
|
export interface PersistentFilterPanelData { |
||||
|
rpcStatus: RpcStatus; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-persistent-filter-panel', |
||||
|
templateUrl: './persistent-filter-panel.component.html', |
||||
|
styleUrls: ['./persistent-filter-panel.component.scss'] |
||||
|
}) |
||||
|
export class PersistentFilterPanelComponent { |
||||
|
|
||||
|
public persistentFilterFormGroup: FormGroup; |
||||
|
public result: PersistentFilterPanelData; |
||||
|
public rpcSearchStatusTranslationMap = rpcStatusTranslation; |
||||
|
public rpcSearchPlaceholder: string; |
||||
|
|
||||
|
public persistentSearchStatuses = Object.keys(RpcStatus); |
||||
|
|
||||
|
constructor(@Inject(PERSISTENT_FILTER_PANEL_DATA) |
||||
|
public data: PersistentFilterPanelData, |
||||
|
public overlayRef: OverlayRef, |
||||
|
private fb: FormBuilder, |
||||
|
private translate: TranslateService) { |
||||
|
this.persistentFilterFormGroup = this.fb.group( |
||||
|
{ |
||||
|
rpcStatus: this.data.rpcStatus |
||||
|
} |
||||
|
); |
||||
|
this.rpcSearchPlaceholder = this.translate.instant('widgets.persistent-table.any-status'); |
||||
|
} |
||||
|
|
||||
|
update() { |
||||
|
this.result = { |
||||
|
rpcStatus: this.persistentFilterFormGroup.get('rpcStatus').value |
||||
|
}; |
||||
|
this.overlayRef.dispose(); |
||||
|
} |
||||
|
|
||||
|
cancel() { |
||||
|
this.overlayRef.dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,126 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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="tb-table-widget tb-absolute-fill"> |
||||
|
<div fxFlex fxLayout="column" class="tb-absolute-fill"> |
||||
|
<div fxFlex class="table-container"> |
||||
|
<table mat-table [dataSource]="persistentDatasource" |
||||
|
matSort [matSortActive]="pageLink.sortOrder.property" |
||||
|
[matSortDirection]="pageLink.sortDirection()" matSortDisableClear> |
||||
|
<ng-container matColumnDef="rpcId"> |
||||
|
<mat-header-cell *matHeaderCellDef class="column-id"> |
||||
|
{{ 'widgets.persistent-table.rpc-id' | translate }} |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column"> |
||||
|
{{ column.id.id }} |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<ng-container matColumnDef="createdTime"> |
||||
|
<mat-header-cell *matHeaderCellDef class="column-time" mat-sort-header> |
||||
|
{{ 'widgets.persistent-table.created-time' | translate }} |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column"> |
||||
|
{{ column.createdTime | date:'yyyy-MM-dd HH:mm:ss' }} |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<ng-container matColumnDef="expirationTime"> |
||||
|
<mat-header-cell *matHeaderCellDef class="column-time" mat-sort-header> |
||||
|
{{ 'widgets.persistent-table.expiration-time' | translate }} |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column"> |
||||
|
{{ column.expirationTime | date:'yyyy-MM-dd HH:mm:ss' }} |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<ng-container matColumnDef="status"> |
||||
|
<mat-header-cell *matHeaderCellDef mat-sort-header> |
||||
|
{{ 'widgets.persistent-table.status' | translate }} |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column" |
||||
|
[ngStyle]="{fontWeight: 'bold', color: rpcStatusColor.get((column.status))}"> |
||||
|
{{ rpcStatusTranslation.get(column.status) | translate }} |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<ng-container matColumnDef="messageType"> |
||||
|
<mat-header-cell *matHeaderCellDef> |
||||
|
{{ 'widgets.persistent-table.message-type' | translate }} |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column"> |
||||
|
{{ 'widgets.persistent-table.message-types.' + column.request.oneway | translate }} |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<ng-container matColumnDef="method"> |
||||
|
<mat-header-cell *matHeaderCellDef> |
||||
|
{{ 'widgets.persistent-table.method' | translate }} |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column"> |
||||
|
{{ column.request.body.method }} |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<ng-container matColumnDef="actions" [stickyEnd]="enableStickyAction"> |
||||
|
<mat-header-cell *matHeaderCellDef> |
||||
|
</mat-header-cell> |
||||
|
<mat-cell *matCellDef="let column"> |
||||
|
<div fxHide fxShow.gt-md fxLayout="row" fxLayoutAlign="end"> |
||||
|
<ng-container *ngFor="let actionDescriptor of actionCellButtonAction"> |
||||
|
<button mat-button mat-icon-button [disabled]="isLoading$ | async" |
||||
|
matTooltip="{{ actionDescriptor.displayName }}" |
||||
|
matTooltipPosition="above" |
||||
|
(click)="onActionButtonClick($event, column, actionDescriptor)"> |
||||
|
<mat-icon>{{ actionDescriptor.icon }}</mat-icon> |
||||
|
</button> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
<div fxHide fxShow.lt-lg *ngIf="actionCellButtonAction.length"> |
||||
|
<button mat-button mat-icon-button |
||||
|
(click)="$event.stopPropagation(); ctx.detectChanges();" |
||||
|
[matMenuTriggerFor]="cellActionsMenu"> |
||||
|
<mat-icon class="material-icons">more_vert</mat-icon> |
||||
|
</button> |
||||
|
<mat-menu #cellActionsMenu="matMenu" xPosition="before"> |
||||
|
<ng-container *ngFor="let actionDescriptor of actionCellButtonAction"> |
||||
|
<button mat-menu-item *ngIf="actionDescriptor.icon" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onActionButtonClick($event, column, actionDescriptor)"> |
||||
|
<mat-icon>{{actionDescriptor.icon}}</mat-icon> |
||||
|
<span>{{ actionDescriptor.displayName }}</span> |
||||
|
</button> |
||||
|
</ng-container> |
||||
|
</mat-menu> |
||||
|
</div> |
||||
|
</mat-cell> |
||||
|
</ng-container> |
||||
|
<mat-header-row *matHeaderRowDef="displayedColumns; sticky: enableStickyHeader"></mat-header-row> |
||||
|
<mat-row *matRowDef="let column; columns: displayedColumns" |
||||
|
[fxShow]="!persistentDatasource.dataLoading"></mat-row> |
||||
|
</table> |
||||
|
<span [fxShow]="(persistentDatasource.isEmpty() | async) && !persistentDatasource.dataLoading" |
||||
|
fxLayoutAlign="center center" |
||||
|
class="no-data-found">{{ noDataDisplayMessageText }}</span> |
||||
|
<span [fxShow]="persistentDatasource.dataLoading" |
||||
|
fxLayoutAlign="center center" |
||||
|
class="no-data-found">{{ 'common.loading' | translate }}</span> |
||||
|
</div> |
||||
|
<mat-divider *ngIf="displayPagination"></mat-divider> |
||||
|
<mat-paginator *ngIf="displayPagination" |
||||
|
[length]="persistentDatasource.total() | async" |
||||
|
[pageIndex]="pageLink.page" |
||||
|
[pageSize]="pageLink.pageSize" |
||||
|
[pageSizeOptions]="pageSizeOptions" |
||||
|
[hidePageSize]="hidePageSize" |
||||
|
showFirstLastButtons></mat-paginator> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,53 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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%; |
||||
|
display: block; |
||||
|
.tb-table-widget { |
||||
|
.table-container { |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.mat-table { |
||||
|
.mat-row { |
||||
|
&.invisible { |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
span.no-data-found { |
||||
|
position: absolute; |
||||
|
top: 60px; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
} |
||||
|
|
||||
|
.column-id { |
||||
|
min-width: 250px; |
||||
|
max-width: 250px; |
||||
|
width: 250px; |
||||
|
} |
||||
|
|
||||
|
.column-time { |
||||
|
min-width: 120px; |
||||
|
max-width: 120px; |
||||
|
width: 120px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,580 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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 { |
||||
|
ChangeDetectorRef, |
||||
|
Component, |
||||
|
ElementRef, |
||||
|
Injector, |
||||
|
Input, |
||||
|
OnInit, |
||||
|
StaticProvider, |
||||
|
ViewChild, |
||||
|
ViewContainerRef |
||||
|
} from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { WidgetContext } from '@home/models/widget-component.models'; |
||||
|
import { WidgetConfig } from '@shared/models/widget.models'; |
||||
|
import { IWidgetSubscription } from '@core/api/widget-api.models'; |
||||
|
import { BehaviorSubject, merge, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; |
||||
|
import { catchError, map, tap } from 'rxjs/operators'; |
||||
|
import { |
||||
|
constructTableCssString, |
||||
|
noDataMessage, |
||||
|
TableCellButtonActionDescriptor, |
||||
|
TableWidgetSettings |
||||
|
} from '@home/components/widget/lib/table-widget.models'; |
||||
|
import cssjs from '@core/css/css'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { hashCode, isDefined, isNumber } from '@core/utils'; |
||||
|
import { CollectionViewer, DataSource } from '@angular/cdk/collections'; |
||||
|
import { emptyPageData, PageData } from '@shared/models/page/page-data'; |
||||
|
import { |
||||
|
PersistentRpc, |
||||
|
PersistentRpcData, |
||||
|
RequestData, |
||||
|
RpcStatus, |
||||
|
rpcStatusColors, |
||||
|
rpcStatusTranslation |
||||
|
} from '@shared/models/rpc.models'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; |
||||
|
import { MatPaginator } from '@angular/material/paginator'; |
||||
|
import { MatSort } from '@angular/material/sort'; |
||||
|
import { NULL_UUID } from '@shared/models/id/has-uuid'; |
||||
|
import { DialogService } from '@core/services/dialog.service'; |
||||
|
import { DeviceService } from '@core/http/device.service'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { |
||||
|
PersistentDetailsDialogComponent, |
||||
|
PersistentDetailsDialogData |
||||
|
} from '@home/components/widget/lib/rpc/persistent-details-dialog.component'; |
||||
|
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; |
||||
|
import { ComponentPortal } from '@angular/cdk/portal'; |
||||
|
import { |
||||
|
PERSISTENT_FILTER_PANEL_DATA, |
||||
|
PersistentFilterPanelComponent, |
||||
|
PersistentFilterPanelData |
||||
|
} from '@home/components/widget/lib/rpc/persistent-filter-panel.component'; |
||||
|
import { PersistentAddDialogComponent } from '@home/components/widget/lib/rpc/persistent-add-dialog.component'; |
||||
|
import { ResizeObserver } from '@juggle/resize-observer'; |
||||
|
import { hidePageSizePixelValue } from '@shared/models/constants'; |
||||
|
import { HttpErrorResponse } from '@angular/common/http'; |
||||
|
|
||||
|
interface PersistentTableWidgetSettings extends TableWidgetSettings { |
||||
|
defaultSortOrder: string; |
||||
|
defaultPageSize: number; |
||||
|
displayPagination: boolean; |
||||
|
enableStickyAction: boolean; |
||||
|
enableStickyHeader: boolean; |
||||
|
enableFilter: boolean; |
||||
|
displayColumns: string[]; |
||||
|
displayDetails: boolean; |
||||
|
allowDelete: boolean; |
||||
|
allowSendRequest: boolean; |
||||
|
} |
||||
|
|
||||
|
interface PersistentTableWidgetActionDescriptor extends TableCellButtonActionDescriptor { |
||||
|
details?: boolean; |
||||
|
delete?: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-persistent-table-widget', |
||||
|
templateUrl: './persistent-table.component.html', |
||||
|
styleUrls: ['./persistent-table.component.scss' , '../table-widget.scss'] |
||||
|
}) |
||||
|
|
||||
|
export class PersistentTableComponent extends PageComponent implements OnInit { |
||||
|
|
||||
|
@Input() |
||||
|
ctx: WidgetContext; |
||||
|
|
||||
|
@ViewChild(MatPaginator) paginator: MatPaginator; |
||||
|
@ViewChild(MatSort) sort: MatSort; |
||||
|
|
||||
|
private settings: PersistentTableWidgetSettings; |
||||
|
private widgetConfig: WidgetConfig; |
||||
|
private subscription: IWidgetSubscription; |
||||
|
private enableFilterAction = true; |
||||
|
private allowSendRequest = true; |
||||
|
private defaultPageSize = 10; |
||||
|
private defaultSortOrder = '-createdTime'; |
||||
|
private rpcStatusFilter: RpcStatus; |
||||
|
private displayDetails = true; |
||||
|
private allowDelete = true; |
||||
|
private displayTableColumns: string[]; |
||||
|
private widgetResize$: ResizeObserver; |
||||
|
|
||||
|
public persistentDatasource: PersistentDatasource; |
||||
|
public noDataDisplayMessageText: string; |
||||
|
public rpcStatusColor = rpcStatusColors; |
||||
|
public rpcStatusTranslation = rpcStatusTranslation; |
||||
|
public displayPagination = true; |
||||
|
public enableStickyHeader = true; |
||||
|
public enableStickyAction = true; |
||||
|
public pageLink: PageLink; |
||||
|
public pageSizeOptions; |
||||
|
public actionCellButtonAction: PersistentTableWidgetActionDescriptor[] = []; |
||||
|
public displayedColumns: string[]; |
||||
|
public hidePageSize = false; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private elementRef: ElementRef, |
||||
|
private overlay: Overlay, |
||||
|
private viewContainerRef: ViewContainerRef, |
||||
|
private utils: UtilsService, |
||||
|
private translate: TranslateService, |
||||
|
private dialogService: DialogService, |
||||
|
private deviceService: DeviceService, |
||||
|
private dialog: MatDialog, |
||||
|
private cd: ChangeDetectorRef) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.ctx.$scope.persistentTableWidget = this; |
||||
|
this.settings = this.ctx.settings; |
||||
|
this.widgetConfig = this.ctx.widgetConfig; |
||||
|
this.subscription = this.ctx.defaultSubscription; |
||||
|
this.initializeConfig(); |
||||
|
this.ctx.updateWidgetParams(); |
||||
|
if (this.displayPagination) { |
||||
|
this.widgetResize$ = new ResizeObserver(() => { |
||||
|
const showHidePageSize = this.elementRef.nativeElement.offsetWidth < hidePageSizePixelValue; |
||||
|
if (showHidePageSize !== this.hidePageSize) { |
||||
|
this.hidePageSize = showHidePageSize; |
||||
|
this.cd.markForCheck(); |
||||
|
} |
||||
|
}); |
||||
|
this.widgetResize$.observe(this.elementRef.nativeElement); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
if (this.widgetResize$) { |
||||
|
this.widgetResize$.disconnect(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
if (this.displayPagination) { |
||||
|
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0); |
||||
|
} |
||||
|
((this.displayPagination ? merge(this.sort.sortChange, this.paginator.page) : this.sort.sortChange) as Observable<any>) |
||||
|
.pipe( |
||||
|
tap(() => this.updateData()) |
||||
|
) |
||||
|
.subscribe(); |
||||
|
this.updateData(); |
||||
|
} |
||||
|
|
||||
|
private initializeConfig() { |
||||
|
|
||||
|
this.displayPagination = isDefined(this.settings.displayPagination) ? this.settings.displayPagination : true; |
||||
|
this.enableStickyHeader = isDefined(this.settings.enableStickyHeader) ? this.settings.enableStickyHeader : true; |
||||
|
this.displayTableColumns = isDefined(this.settings.displayColumns) ? this.settings.displayColumns : []; |
||||
|
this.enableStickyAction = isDefined(this.settings.enableStickyAction) ? this.settings.enableStickyAction : true; |
||||
|
this.enableFilterAction = isDefined(this.settings.enableFilter) ? this.settings.enableFilter : true; |
||||
|
this.displayDetails = isDefined(this.settings.displayDetails) ? this.settings.displayDetails : true; |
||||
|
this.allowDelete = isDefined(this.settings.allowDelete) ? this.settings.allowDelete : true; |
||||
|
this.allowSendRequest = isDefined(this.settings.allowSendRequest) ? this.settings.allowSendRequest : true; |
||||
|
|
||||
|
this.noDataDisplayMessageText = |
||||
|
noDataMessage(this.widgetConfig.noDataDisplayMessage, 'widgets.persistent-table.no-request-prompt', this.utils, this.translate); |
||||
|
|
||||
|
this.displayedColumns = [...this.displayTableColumns]; |
||||
|
|
||||
|
const pageSize = this.settings.defaultPageSize; |
||||
|
if (isDefined(pageSize) && isNumber(pageSize) && pageSize > 0) { |
||||
|
this.defaultPageSize = pageSize; |
||||
|
} |
||||
|
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; |
||||
|
if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { |
||||
|
this.defaultSortOrder = this.settings.defaultSortOrder; |
||||
|
} |
||||
|
const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); |
||||
|
this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder); |
||||
|
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : 1024; |
||||
|
|
||||
|
|
||||
|
this.ctx.widgetActions = [ |
||||
|
{ |
||||
|
name: 'widgets.persistent-table.add', |
||||
|
show: this.allowSendRequest, |
||||
|
icon: 'add', |
||||
|
onAction: $event => this.addPersistentRpcRequest($event) |
||||
|
}, |
||||
|
{ |
||||
|
name: 'widgets.persistent-table.refresh', |
||||
|
show: true, |
||||
|
icon: 'refresh', |
||||
|
onAction: () => this.reloadPersistentRequests() |
||||
|
}, |
||||
|
{ |
||||
|
name: 'widgets.persistent-table.filter', |
||||
|
show: this.enableFilterAction, |
||||
|
icon: 'filter_list', |
||||
|
onAction: $event => this.editFilter($event) |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
if (this.settings.displayDetails) { |
||||
|
this.actionCellButtonAction.push( |
||||
|
{ |
||||
|
displayName: this.translate.instant('widgets.persistent-table.details'), |
||||
|
icon: 'more_horiz', |
||||
|
details: true |
||||
|
} as PersistentTableWidgetActionDescriptor |
||||
|
); |
||||
|
} |
||||
|
if (this.settings.allowDelete) { |
||||
|
this.actionCellButtonAction.push( |
||||
|
{ |
||||
|
displayName: this.translate.instant('widgets.persistent-table.delete'), |
||||
|
icon: 'delete', |
||||
|
delete: true |
||||
|
} as PersistentTableWidgetActionDescriptor |
||||
|
); |
||||
|
} |
||||
|
if (this.actionCellButtonAction.length) { |
||||
|
this.displayedColumns.push('actions'); |
||||
|
} |
||||
|
|
||||
|
this.persistentDatasource = new PersistentDatasource(this.translate, this.subscription, this.ctx); |
||||
|
|
||||
|
const cssString = constructTableCssString(this.widgetConfig); |
||||
|
const cssParser = new cssjs(); |
||||
|
cssParser.testMode = false; |
||||
|
const namespace = 'persistent-table-' + hashCode(cssString); |
||||
|
cssParser.cssPreviewNamespace = namespace; |
||||
|
cssParser.createStyleElement(namespace, cssString); |
||||
|
$(this.elementRef.nativeElement).addClass(namespace); |
||||
|
} |
||||
|
|
||||
|
private updateData() { |
||||
|
if (this.displayPagination) { |
||||
|
this.pageLink.page = this.paginator.pageIndex; |
||||
|
this.pageLink.pageSize = this.paginator.pageSize; |
||||
|
} else { |
||||
|
this.pageLink.page = 0; |
||||
|
} |
||||
|
if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { |
||||
|
this.defaultSortOrder = this.utils.customTranslation(this.settings.defaultSortOrder, this.settings.defaultSortOrder); |
||||
|
} |
||||
|
this.pageLink.sortOrder.property = this.sort.active; |
||||
|
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()]; |
||||
|
this.persistentDatasource.loadPersistent(this.pageLink, this.rpcStatusFilter); |
||||
|
this.ctx.detectChanges(); |
||||
|
} |
||||
|
|
||||
|
public onDataUpdated() { |
||||
|
this.ctx.detectChanges(); |
||||
|
} |
||||
|
|
||||
|
reloadPersistentRequests() { |
||||
|
if (this.displayPagination) { |
||||
|
this.paginator.pageIndex = 0; |
||||
|
} |
||||
|
this.updateData(); |
||||
|
} |
||||
|
|
||||
|
deleteRpcRequest($event: Event, persistentRpc: PersistentRpc) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
if (persistentRpc && persistentRpc.id && persistentRpc.id.id !== NULL_UUID) { |
||||
|
this.dialogService.confirm( |
||||
|
this.translate.instant('widgets.persistent-table.delete-request-title'), |
||||
|
this.translate.instant('widgets.persistent-table.delete-request-text'), |
||||
|
this.translate.instant('action.no'), |
||||
|
this.translate.instant('action.yes') |
||||
|
).subscribe((res) => { |
||||
|
if (res) { |
||||
|
this.deviceService.deletePersistedRpc(persistentRpc.id.id).subscribe(() => { |
||||
|
this.reloadPersistentRequests(); |
||||
|
}); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
openRequestDetails($event: Event, persistentRpc: PersistentRpc) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
if (persistentRpc && persistentRpc.id && persistentRpc.id.id !== NULL_UUID) { |
||||
|
this.dialog.open<PersistentDetailsDialogComponent, PersistentDetailsDialogData, boolean> |
||||
|
(PersistentDetailsDialogComponent, |
||||
|
{ |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
persistentRequest: persistentRpc, |
||||
|
allowDelete: this.allowDelete |
||||
|
} |
||||
|
}).afterClosed().subscribe( |
||||
|
(res) => { |
||||
|
if (res) { |
||||
|
this.reloadPersistentRequests(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
addPersistentRpcRequest($event: Event){ |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
this.dialog.open<PersistentAddDialogComponent, RequestData> |
||||
|
(PersistentAddDialogComponent, |
||||
|
{ |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'] |
||||
|
}).afterClosed().subscribe( |
||||
|
(requestData) => { |
||||
|
if (requestData) { |
||||
|
this.sendRequests(requestData); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private sendRequests(requestData: RequestData) { |
||||
|
let commandPromise; |
||||
|
if (requestData.oneWayElseTwoWay) { |
||||
|
commandPromise = this.ctx.controlApi.sendOneWayCommand( |
||||
|
requestData.method, |
||||
|
requestData.params, null, |
||||
|
true, null, |
||||
|
requestData.retries, |
||||
|
requestData.additionalInfo |
||||
|
); |
||||
|
} else { |
||||
|
commandPromise = this.ctx.controlApi.sendTwoWayCommand( |
||||
|
requestData.method, |
||||
|
requestData.params, |
||||
|
null, |
||||
|
true, null, |
||||
|
requestData.retries, |
||||
|
requestData.additionalInfo |
||||
|
); |
||||
|
} |
||||
|
commandPromise.subscribe( |
||||
|
() => { |
||||
|
this.reloadPersistentRequests(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
public onActionButtonClick($event: Event, persistentRpc: PersistentRpc, actionDescriptor: PersistentTableWidgetActionDescriptor) { |
||||
|
if (actionDescriptor.details) { |
||||
|
this.openRequestDetails($event, persistentRpc); |
||||
|
} |
||||
|
if (actionDescriptor.delete) { |
||||
|
this.deleteRpcRequest($event, persistentRpc); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private editFilter($event: Event) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
const target = $event.target || $event.srcElement || $event.currentTarget; |
||||
|
const config = new OverlayConfig(); |
||||
|
config.backdropClass = 'cdk-overlay-transparent-backdrop'; |
||||
|
config.hasBackdrop = true; |
||||
|
const connectedPosition: ConnectedPosition = { |
||||
|
originX: 'end', |
||||
|
originY: 'bottom', |
||||
|
overlayX: 'end', |
||||
|
overlayY: 'top' |
||||
|
}; |
||||
|
config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) |
||||
|
.withPositions([connectedPosition]); |
||||
|
|
||||
|
const overlayRef = this.overlay.create(config); |
||||
|
overlayRef.backdropClick().subscribe(() => { |
||||
|
overlayRef.dispose(); |
||||
|
}); |
||||
|
const providers: StaticProvider[] = [ |
||||
|
{ |
||||
|
provide: PERSISTENT_FILTER_PANEL_DATA, |
||||
|
useValue: { |
||||
|
rpcStatus: this.rpcStatusFilter |
||||
|
} as PersistentFilterPanelData |
||||
|
}, |
||||
|
{ |
||||
|
provide: OverlayRef, |
||||
|
useValue: overlayRef |
||||
|
} |
||||
|
]; |
||||
|
const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); |
||||
|
const componentRef = overlayRef.attach(new ComponentPortal(PersistentFilterPanelComponent, |
||||
|
this.viewContainerRef, injector)); |
||||
|
componentRef.onDestroy(() => { |
||||
|
if (componentRef.instance.result) { |
||||
|
const result = componentRef.instance.result; |
||||
|
this.rpcStatusFilter = result.rpcStatus; |
||||
|
this.reloadPersistentRequests(); |
||||
|
} |
||||
|
}); |
||||
|
this.ctx.detectChanges(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
class PersistentDatasource implements DataSource<PersistentRpcData> { |
||||
|
|
||||
|
private persistentSubject = new BehaviorSubject<PersistentRpcData[]>([]); |
||||
|
private pageDataSubject = new BehaviorSubject<PageData<PersistentRpcData>>(emptyPageData<PersistentRpcData>()); |
||||
|
|
||||
|
private rpcErrorText: string; |
||||
|
private executingSubjects: Array<Subject<any>>; |
||||
|
private executingRpcRequest = false; |
||||
|
|
||||
|
public dataLoading = true; |
||||
|
public pageData$ = this.pageDataSubject.asObservable(); |
||||
|
|
||||
|
constructor(private translate: TranslateService, |
||||
|
private subscription: IWidgetSubscription, |
||||
|
private ctx: WidgetContext) { |
||||
|
} |
||||
|
|
||||
|
connect(collectionViewer: CollectionViewer): Observable<PersistentRpcData[] | ReadonlyArray<PersistentRpcData>> { |
||||
|
return this.persistentSubject.asObservable(); |
||||
|
} |
||||
|
|
||||
|
disconnect(collectionViewer: CollectionViewer): void { |
||||
|
this.persistentSubject.complete(); |
||||
|
this.pageDataSubject.complete(); |
||||
|
} |
||||
|
|
||||
|
reset() { |
||||
|
const pageData = emptyPageData<PersistentRpcData>(); |
||||
|
this.persistentSubject.next(pageData.data); |
||||
|
this.pageDataSubject.next(pageData); |
||||
|
} |
||||
|
|
||||
|
loadPersistent(pageLink: PageLink, rpcStatusFilter: RpcStatus) { |
||||
|
this.dataLoading = true; |
||||
|
|
||||
|
const result = new ReplaySubject<PageData<PersistentRpcData>>(); |
||||
|
this.fetchEntities(pageLink, rpcStatusFilter).pipe( |
||||
|
catchError(() => of(emptyPageData<PersistentRpcData>())), |
||||
|
).subscribe( |
||||
|
(pageData) => { |
||||
|
this.persistentSubject.next(pageData.data); |
||||
|
this.pageDataSubject.next(pageData); |
||||
|
result.next(pageData); |
||||
|
this.dataLoading = false; |
||||
|
} |
||||
|
); |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
fetchEntities(pageLink: PageLink, rpcStatusFilter: RpcStatus): Observable<PageData<PersistentRpcData>> { |
||||
|
if (!this.subscription.rpcEnabled) { |
||||
|
return throwError(new Error('Rpc disabled!')); |
||||
|
} else if (!this.subscription.targetDeviceId) { |
||||
|
return throwError(new Error('Target device is not set!')); |
||||
|
} |
||||
|
const rpcSubject: Subject<any> = new Subject<any>(); |
||||
|
|
||||
|
this.ctx.deviceService.getPersistedRpcRequests(this.subscription.targetDeviceId, pageLink, rpcStatusFilter).subscribe( |
||||
|
(responseBody) => { |
||||
|
rpcSubject.next(responseBody); |
||||
|
rpcSubject.complete(); |
||||
|
}, |
||||
|
(rejection: HttpErrorResponse) => { |
||||
|
this.rpcErrorText = null; |
||||
|
this.executingSubjects = []; |
||||
|
|
||||
|
const index = this.executingSubjects.indexOf(rpcSubject); |
||||
|
if (index >= 0) { |
||||
|
this.executingSubjects.splice(index, 1); |
||||
|
} |
||||
|
this.executingRpcRequest = this.executingSubjects.length > 0; |
||||
|
this.subscription.options.callbacks.rpcStateChanged(this.subscription); |
||||
|
if (!this.executingRpcRequest || rejection.status === 504) { |
||||
|
this.subscription.rpcRejection = rejection; |
||||
|
if (rejection.status === 504) { |
||||
|
this.subscription.rpcErrorText = 'Request Timeout.'; |
||||
|
} else { |
||||
|
this.subscription.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; |
||||
|
const error = this.extractRejectionErrorText(rejection); |
||||
|
if (error) { |
||||
|
this.subscription.rpcErrorText += '</br>'; |
||||
|
this.subscription.rpcErrorText += error.message || ''; |
||||
|
} |
||||
|
} |
||||
|
this.subscription.callbacks.onRpcFailed(this.subscription); |
||||
|
} |
||||
|
rpcSubject.error(rejection); |
||||
|
} |
||||
|
); |
||||
|
return rpcSubject.asObservable(); |
||||
|
} |
||||
|
|
||||
|
extractRejectionErrorText(rejection: HttpErrorResponse) { |
||||
|
let error = null; |
||||
|
if (rejection.error) { |
||||
|
error = rejection.error; |
||||
|
try { |
||||
|
error = rejection.error ? JSON.parse(rejection.error) : null; |
||||
|
} catch (e) {} |
||||
|
} |
||||
|
if (error && !error.message) { |
||||
|
error = this.prepareMessageFromData(error); |
||||
|
} else if (error && error.message) { |
||||
|
error = error.message; |
||||
|
} |
||||
|
return error; |
||||
|
} |
||||
|
|
||||
|
prepareMessageFromData(data) { |
||||
|
if (typeof data === 'object' && data.constructor === ArrayBuffer) { |
||||
|
const msg = String.fromCharCode.apply(null, new Uint8Array(data)); |
||||
|
try { |
||||
|
const msgObj = JSON.parse(msg); |
||||
|
if (msgObj.message) { |
||||
|
return msgObj.message; |
||||
|
} else { |
||||
|
return msg; |
||||
|
} |
||||
|
} catch (e) { |
||||
|
return msg; |
||||
|
} |
||||
|
} else { |
||||
|
return data; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
isEmpty(): Observable<boolean> { |
||||
|
return this.persistentSubject.pipe( |
||||
|
map((requests) => !requests.length) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
total(): Observable<number> { |
||||
|
return this.pageDataSubject.pipe( |
||||
|
map((pageData) => pageData.totalElements) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2021 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 style="background: #fff;" [ngClass]="{'fill-height': fillHeight}"> |
||||
|
<label class="tb-title no-padding" *ngIf="label">{{ label }}</label> |
||||
|
<span fxFlex></span> |
||||
|
<div #jsonViewer id="tb-json-view" [ngClass]="{'fill-height': fillHeight}"></div> |
||||
|
</div> |
||||
@ -0,0 +1,27 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2021 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 { |
||||
|
#tb-json-view { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
margin-bottom: 16px; |
||||
|
border: 1px solid #c0c0c0; |
||||
|
|
||||
|
&:not(.fill-height) { |
||||
|
min-height: 100px; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,165 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2021 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, ElementRef, forwardRef, Input, OnInit, Renderer2, ViewChild } from '@angular/core'; |
||||
|
import { NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { Ace } from 'ace-builds'; |
||||
|
import { coerceBooleanProperty } from '@angular/cdk/coercion'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { RafService } from '@core/services/raf.service'; |
||||
|
import { isDefinedAndNotNull, isUndefined } from '@core/utils'; |
||||
|
import { getAce } from '@shared/models/ace/ace.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-json-object-view', |
||||
|
templateUrl: './json-object-view.component.html', |
||||
|
styleUrls: ['./json-object-view.component.scss'], |
||||
|
providers: [ |
||||
|
{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => JsonObjectViewComponent), |
||||
|
multi: true |
||||
|
} |
||||
|
] |
||||
|
}) |
||||
|
export class JsonObjectViewComponent implements OnInit { |
||||
|
|
||||
|
@ViewChild('jsonViewer', {static: true}) |
||||
|
jsonViewerElmRef: ElementRef; |
||||
|
|
||||
|
private jsonViewer: Ace.Editor; |
||||
|
private viewerElement: Ace.Editor; |
||||
|
private propagateChange = null; |
||||
|
private modelValue: any; |
||||
|
private contentValue: string; |
||||
|
|
||||
|
@Input() label: string; |
||||
|
|
||||
|
@Input() fillHeight: boolean; |
||||
|
|
||||
|
@Input() editorStyle: { [klass: string]: any }; |
||||
|
|
||||
|
@Input() sort: (key: string, value: any) => any; |
||||
|
|
||||
|
private widthValue: boolean; |
||||
|
|
||||
|
get autoWidth(): boolean { |
||||
|
return this.widthValue; |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
set autoWidth(value: boolean) { |
||||
|
this.widthValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
private heigthValue: boolean; |
||||
|
|
||||
|
get autoHeight(): boolean { |
||||
|
return this.heigthValue; |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
set autoHeight(value: boolean) { |
||||
|
this.heigthValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
constructor(public elementRef: ElementRef, |
||||
|
protected store: Store<AppState>, |
||||
|
private raf: RafService, |
||||
|
private renderer: Renderer2) { |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.viewerElement = this.jsonViewerElmRef.nativeElement; |
||||
|
let editorOptions: Partial<Ace.EditorOptions> = { |
||||
|
mode: 'ace/mode/java', |
||||
|
theme: 'ace/theme/github', |
||||
|
showGutter: false, |
||||
|
showPrintMargin: false, |
||||
|
readOnly: true |
||||
|
}; |
||||
|
|
||||
|
const advancedOptions = { |
||||
|
enableSnippets: false, |
||||
|
enableBasicAutocompletion: false, |
||||
|
enableLiveAutocompletion: false |
||||
|
}; |
||||
|
|
||||
|
editorOptions = {...editorOptions, ...advancedOptions}; |
||||
|
getAce().subscribe( |
||||
|
(ace) => { |
||||
|
this.jsonViewer = ace.edit(this.viewerElement, editorOptions); |
||||
|
this.jsonViewer.session.setUseWrapMode(false); |
||||
|
this.jsonViewer.setValue(this.contentValue ? this.contentValue : '', -1); |
||||
|
if (this.contentValue && (this.widthValue || this.heigthValue)) { |
||||
|
this.updateEditorSize(this.viewerElement, this.contentValue, this.jsonViewer); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
updateEditorSize(editorElement: any, content: string, editor: Ace.Editor) { |
||||
|
let newHeight = 200; |
||||
|
let newWidth = 600; |
||||
|
if (content && content.length > 0) { |
||||
|
const lines = content.split('\n'); |
||||
|
newHeight = 17 * lines.length + 17; |
||||
|
let maxLineLength = 0; |
||||
|
lines.forEach((row) => { |
||||
|
const line = row.replace(/\t/g, ' ').replace(/\n/g, ''); |
||||
|
const lineLength = line.length; |
||||
|
maxLineLength = Math.max(maxLineLength, lineLength); |
||||
|
}); |
||||
|
newWidth = 8 * maxLineLength + 16; |
||||
|
} |
||||
|
if (this.heigthValue) { |
||||
|
this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); |
||||
|
} |
||||
|
if (this.widthValue) { |
||||
|
this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); |
||||
|
} |
||||
|
editor.resize(); |
||||
|
} |
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
writeValue(value: any): void { |
||||
|
this.modelValue = value; |
||||
|
this.contentValue = ''; |
||||
|
try { |
||||
|
if (isDefinedAndNotNull(this.modelValue)) { |
||||
|
this.contentValue = JSON.stringify(this.modelValue, isUndefined(this.sort) ? undefined : |
||||
|
(key, objectValue) => { |
||||
|
return this.sort(key, objectValue); |
||||
|
}, 2); |
||||
|
} |
||||
|
} catch (e) { |
||||
|
console.error(e); |
||||
|
} |
||||
|
if (this.jsonViewer) { |
||||
|
this.jsonViewer.setValue(this.contentValue ? this.contentValue : '', -1); |
||||
|
if (this.contentValue && (this.widthValue || this.heigthValue)) { |
||||
|
this.updateEditorSize(this.viewerElement, this.contentValue, this.jsonViewer); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue