Browse Source

Relation edit dialog improvements. Minor improvements.

pull/2147/head
Igor Kulikov 7 years ago
parent
commit
52ab3d6dea
  1. 13
      ui-ngx/src/app/core/guards/auth.guard.ts
  2. 23
      ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
  3. 13
      ui-ngx/src/app/core/notification/notification.actions.ts
  4. 9
      ui-ngx/src/app/core/notification/notification.effects.ts
  5. 5
      ui-ngx/src/app/core/notification/notification.models.ts
  6. 5
      ui-ngx/src/app/core/notification/notification.reducer.ts
  7. 28
      ui-ngx/src/app/core/services/dialog.service.ts
  8. 13
      ui-ngx/src/app/core/services/notification.service.ts
  9. 9
      ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts
  10. 7
      ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts
  11. 7
      ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html
  12. 31
      ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts
  13. 8
      ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts
  14. 8
      ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts
  15. 4
      ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html
  16. 4
      ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html
  17. 7
      ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts
  18. 8
      ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts
  19. 8
      ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts
  20. 4
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html
  21. 7
      ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts
  22. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html
  23. 4
      ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html
  24. 7
      ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts
  25. 7
      ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts
  26. 51
      ui-ngx/src/app/shared/components/dialog.component.ts
  27. 1
      ui-ngx/src/app/shared/components/entity/entity-list.component.html
  28. 26
      ui-ngx/src/app/shared/components/entity/entity-list.component.ts
  29. 21
      ui-ngx/src/app/shared/components/fullscreen.directive.ts
  30. 6
      ui-ngx/src/app/shared/components/json-object-edit.component.html
  31. 23
      ui-ngx/src/app/shared/components/json-object-edit.component.scss
  32. 52
      ui-ngx/src/app/shared/components/json-object-edit.component.ts
  33. 4
      ui-ngx/src/app/shared/components/page.component.ts
  34. 15
      ui-ngx/src/app/shared/components/toast.directive.ts

13
ui-ngx/src/app/core/guards/auth.guard.ts

@ -82,18 +82,7 @@ export class AuthGuard implements CanActivate, CanActivateChild {
} else { } else {
const authority = Authority[authState.authUser.authority]; const authority = Authority[authState.authUser.authority];
if (data.auth && data.auth.indexOf(authority) === -1) { if (data.auth && data.auth.indexOf(authority) === -1) {
this.dialogService.confirm( this.dialogService.forbidden();
this.translate.instant('access.access-forbidden'),
this.translate.instant('access.access-forbidden-text'),
this.translate.instant('action.cancel'),
this.translate.instant('action.sign-in'),
true
).subscribe((res) => {
if (res) {
this.authService.logout();
}
}
);
return false; return false;
} else { } else {
return true; return true;

23
ui-ngx/src/app/core/interceptors/global-http-interceptor.ts

@ -50,6 +50,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
'/api/auth/token' '/api/auth/token'
]; ];
private activeRequests = 0;
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
private dialogService: DialogService, private dialogService: DialogService,
private translate: TranslateService, private translate: TranslateService,
@ -135,7 +137,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
} }
} else if (errorResponse.status === 403) { } else if (errorResponse.status === 403) {
if (!ignoreErrors) { if (!ignoreErrors) {
this.permissionDenied(); this.dialogService.forbidden();
} }
} else if (errorResponse.status === 0 || errorResponse.status === -1) { } else if (errorResponse.status === 0 || errorResponse.status === -1) {
this.showError('Unable to connect'); this.showError('Unable to connect');
@ -241,7 +243,16 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
private updateLoadingState(config: InterceptorConfig, isLoading: boolean) { private updateLoadingState(config: InterceptorConfig, isLoading: boolean) {
if (!config.ignoreLoading) { if (!config.ignoreLoading) {
this.store.dispatch(isLoading ? new ActionLoadStart() : new ActionLoadFinish()); if (isLoading) {
this.activeRequests++;
} else {
this.activeRequests--;
}
if (this.activeRequests === 1) {
this.store.dispatch(new ActionLoadStart());
} else if (this.activeRequests === 0) {
this.store.dispatch(new ActionLoadFinish());
}
} }
} }
@ -253,14 +264,6 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
} }
} }
private permissionDenied() {
this.dialogService.alert(
this.translate.instant('access.permission-denied'),
this.translate.instant('access.permission-denied-text'),
this.translate.instant('action.close')
);
}
private showError(error: string, timeout: number = 0) { private showError(error: string, timeout: number = 0) {
setTimeout(() => { setTimeout(() => {
this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'})); this.store.dispatch(new ActionNotificationShow({message: error, type: 'error'}));

13
ui-ngx/src/app/core/notification/notification.actions.ts

@ -15,10 +15,11 @@
/// ///
import { Action } from '@ngrx/store'; import { Action } from '@ngrx/store';
import { NotificationMessage } from '@app/core/notification/notification.models'; import { NotificationMessage, HideNotification } from '@app/core/notification/notification.models';
export enum NotificationActionTypes { export enum NotificationActionTypes {
SHOW_NOTIFICATION = '[Notification] Show' SHOW_NOTIFICATION = '[Notification] Show',
HIDE_NOTIFICATION = '[Notification] Hide'
} }
export class ActionNotificationShow implements Action { export class ActionNotificationShow implements Action {
@ -27,5 +28,11 @@ export class ActionNotificationShow implements Action {
constructor(readonly notification: NotificationMessage ) {} constructor(readonly notification: NotificationMessage ) {}
} }
export class ActionNotificationHide implements Action {
readonly type = NotificationActionTypes.HIDE_NOTIFICATION;
constructor(readonly hideNotification: HideNotification ) {}
}
export type NotificationActions = export type NotificationActions =
| ActionNotificationShow; | ActionNotificationShow | ActionNotificationHide;

9
ui-ngx/src/app/core/notification/notification.effects.ts

@ -44,4 +44,13 @@ export class NotificationEffects {
}) })
); );
@Effect({dispatch: false})
hideNotification = this.actions$.pipe(
ofType(
NotificationActionTypes.HIDE_NOTIFICATION,
),
map(({ hideNotification }) => {
this.notificationService.hideNotification(hideNotification);
})
);
} }

5
ui-ngx/src/app/core/notification/notification.models.ts

@ -17,6 +17,7 @@
export interface NotificationState { export interface NotificationState {
notification: NotificationMessage; notification: NotificationMessage;
hideNotification: HideNotification;
} }
export declare type NotificationType = 'info' | 'success' | 'error'; export declare type NotificationType = 'info' | 'success' | 'error';
@ -31,3 +32,7 @@ export class NotificationMessage {
horizontalPosition?: NotificationHorizontalPosition; horizontalPosition?: NotificationHorizontalPosition;
verticalPosition?: NotificationVerticalPosition; verticalPosition?: NotificationVerticalPosition;
} }
export class HideNotification {
target?: string;
}

5
ui-ngx/src/app/core/notification/notification.reducer.ts

@ -18,7 +18,8 @@ import { NotificationState } from './notification.models';
import { NotificationActions, NotificationActionTypes } from './notification.actions'; import { NotificationActions, NotificationActionTypes } from './notification.actions';
export const initialState: NotificationState = { export const initialState: NotificationState = {
notification: null notification: null,
hideNotification: null
}; };
export function notificationReducer( export function notificationReducer(
@ -28,6 +29,8 @@ export function notificationReducer(
switch (action.type) { switch (action.type) {
case NotificationActionTypes.SHOW_NOTIFICATION: case NotificationActionTypes.SHOW_NOTIFICATION:
return { ...state, notification: action.notification }; return { ...state, notification: action.notification };
case NotificationActionTypes.HIDE_NOTIFICATION:
return { ...state, hideNotification: action.hideNotification };
default: default:
return state; return state;
} }

28
ui-ngx/src/app/core/services/dialog.service.ts

@ -20,7 +20,8 @@ import { MatDialog, MatDialogConfig } from '@angular/material';
import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component'; import { ConfirmDialogComponent } from '@core/services/dialog/confirm-dialog.component';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component'; import { AlertDialogComponent } from '@core/services/dialog/alert-dialog.component';
import {TodoDialogComponent} from "@core/services/dialog/todo-dialog.component"; import { TodoDialogComponent } from '@core/services/dialog/todo-dialog.component';
import { AuthService } from '@core/auth/auth.service';
@Injectable( @Injectable(
{ {
@ -31,6 +32,7 @@ export class DialogService {
constructor( constructor(
private translate: TranslateService, private translate: TranslateService,
private authService: AuthService,
public dialog: MatDialog public dialog: MatDialog
) { ) {
} }
@ -68,6 +70,30 @@ export class DialogService {
return dialogRef.afterClosed(); return dialogRef.afterClosed();
} }
private permissionDenied() {
this.alert(
this.translate.instant('access.permission-denied'),
this.translate.instant('access.permission-denied-text'),
this.translate.instant('action.close')
);
}
forbidden(): Observable<boolean> {
const observable = this.confirm(
this.translate.instant('access.access-forbidden'),
this.translate.instant('access.access-forbidden-text'),
this.translate.instant('action.cancel'),
this.translate.instant('action.sign-in'),
true
);
observable.subscribe((res) => {
if (res) {
this.authService.logout();
}
});
return observable;
}
todo(): Observable<any> { todo(): Observable<any> {
const dialogConfig: MatDialogConfig = { const dialogConfig: MatDialogConfig = {
disableClose: true, disableClose: true,

13
ui-ngx/src/app/core/services/notification.service.ts

@ -15,7 +15,7 @@
/// ///
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { NotificationMessage } from '@app/core/notification/notification.models'; import { HideNotification, NotificationMessage } from '@app/core/notification/notification.models';
import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { BehaviorSubject, Observable, Subject } from 'rxjs';
@ -28,6 +28,8 @@ export class NotificationService {
private notificationSubject: Subject<NotificationMessage> = new Subject(); private notificationSubject: Subject<NotificationMessage> = new Subject();
private hideNotificationSubject: Subject<HideNotification> = new Subject();
constructor( constructor(
) { ) {
} }
@ -36,8 +38,15 @@ export class NotificationService {
this.notificationSubject.next(notification); this.notificationSubject.next(notification);
} }
hideNotification(hideNotification: HideNotification) {
this.hideNotificationSubject.next(hideNotification);
}
getNotification(): Observable<NotificationMessage> { getNotification(): Observable<NotificationMessage> {
return this.notificationSubject; return this.notificationSubject.asObservable();
} }
getHideNotification(): Observable<HideNotification> {
return this.hideNotificationSubject.asObservable();
}
} }

9
ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts

@ -30,6 +30,8 @@ import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { ActionStatus, AuditLog } from '@shared/models/audit-log.models'; import { ActionStatus, AuditLog } from '@shared/models/audit-log.models';
import * as ace from 'ace-builds'; import * as ace from 'ace-builds';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface AuditLogDetailsDialogData { export interface AuditLogDetailsDialogData {
auditLog: AuditLog; auditLog: AuditLog;
@ -40,7 +42,7 @@ export interface AuditLogDetailsDialogData {
templateUrl: './audit-log-details-dialog.component.html', templateUrl: './audit-log-details-dialog.component.html',
styleUrls: ['./audit-log-details-dialog.component.scss'] styleUrls: ['./audit-log-details-dialog.component.scss']
}) })
export class AuditLogDetailsDialogComponent extends PageComponent implements OnInit { export class AuditLogDetailsDialogComponent extends DialogComponent<AuditLogDetailsDialogComponent> implements OnInit {
@ViewChild('actionDataEditor', {static: true}) @ViewChild('actionDataEditor', {static: true})
actionDataEditorElmRef: ElementRef; actionDataEditorElmRef: ElementRef;
@ -58,10 +60,11 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI
@ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData, @Inject(MAT_DIALOG_DATA) public data: AuditLogDetailsDialogData,
public dialogRef: MatDialogRef<AuditLogDetailsDialogComponent>, public dialogRef: MatDialogRef<AuditLogDetailsDialogComponent>,
private renderer: Renderer2) { private renderer: Renderer2) {
super(store); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {
@ -114,7 +117,7 @@ export class AuditLogDetailsDialogComponent extends PageComponent implements OnI
}); });
newWidth = 8 * maxLineLength + 16; newWidth = 8 * maxLineLength + 16;
} }
newHeight = Math.min(400, newHeight); // newHeight = Math.min(400, newHeight);
this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px'); this.renderer.setStyle(editorElement, 'minHeight', newHeight.toString() + 'px');
this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px'); this.renderer.setStyle(editorElement, 'height', newHeight.toString() + 'px');
this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px'); this.renderer.setStyle(editorElement, 'width', newWidth.toString() + 'px');

7
ui-ngx/src/app/modules/home/components/entity/add-entity-dialog.component.ts

@ -27,6 +27,8 @@ import {TbAnchorComponent} from '@shared/components/tb-anchor.component';
import {EntityComponent} from './entity.component'; import {EntityComponent} from './entity.component';
import {EntityTableConfig} from '@home/models/entity/entities-table-config.models'; import {EntityTableConfig} from '@home/models/entity/entities-table-config.models';
import {AddEntityDialogData} from '@home/models/entity/entity-component.models'; import {AddEntityDialogData} from '@home/models/entity/entity-component.models';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'tb-add-entity-dialog', selector: 'tb-add-entity-dialog',
@ -34,7 +36,7 @@ import {AddEntityDialogData} from '@home/models/entity/entity-component.models';
providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: AddEntityDialogComponent}],
styleUrls: ['./add-entity-dialog.component.scss'] styleUrls: ['./add-entity-dialog.component.scss']
}) })
export class AddEntityDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { export class AddEntityDialogComponent extends DialogComponent<AddEntityDialogComponent, BaseData<HasId>> implements OnInit, ErrorStateMatcher {
entityComponent: EntityComponent<BaseData<HasId>>; entityComponent: EntityComponent<BaseData<HasId>>;
detailsForm: NgForm; detailsForm: NgForm;
@ -49,11 +51,12 @@ export class AddEntityDialogComponent extends PageComponent implements OnInit, E
@ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent; @ViewChild('entityDetailsForm', {static: true}) entityDetailsFormAnchor: TbAnchorComponent;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<HasId>>, @Inject(MAT_DIALOG_DATA) public data: AddEntityDialogData<BaseData<HasId>>,
public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>, public dialogRef: MatDialogRef<AddEntityDialogComponent, BaseData<HasId>>,
private componentFactoryResolver: ComponentFactoryResolver, private componentFactoryResolver: ComponentFactoryResolver,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher) { @SkipSelf() private errorStateMatcher: ErrorStateMatcher) {
super(store); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {

7
ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html

@ -41,11 +41,12 @@
required="true"> required="true">
</tb-entity-list-select> </tb-entity-list-select>
<tb-json-object-edit <tb-json-object-edit
formControlName="additionalInfo" #additionalInfoEdit
[formControl]="additionalInfo"
label="{{ 'relation.additional-info' | translate }}"> label="{{ 'relation.additional-info' | translate }}">
</tb-json-object-edit> </tb-json-object-edit>
<div class="tb-error-messages" *ngIf="submitted && <div class="tb-error-messages" *ngIf="submitted &&
relationFormGroup.get('additionalInfo').invalid" role="alert"> additionalInfo.invalid" role="alert">
<div translate class="tb-error-message">relation.invalid-additional-info</div> <div translate class="tb-error-message">relation.invalid-additional-info</div>
</div> </div>
</fieldset> </fieldset>
@ -54,7 +55,7 @@
<span fxFlex></span> <span fxFlex></span>
<button mat-button mat-raised-button color="primary" <button mat-button mat-raised-button color="primary"
type="submit" type="submit"
[disabled]="(isLoading$ | async)"> [disabled]="(isLoading$ | async) || relationFormGroup.invalid || !(relationFormGroup.dirty || additionalInfo.dirty)">
{{ (isAdd ? 'action.add' : 'action.save') | translate }} {{ (isAdd ? 'action.add' : 'action.save') | translate }}
</button> </button>
<button mat-button color="primary" <button mat-button color="primary"

31
ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.ts

@ -14,9 +14,8 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; import { Component, Inject, OnInit, SkipSelf, ViewChild } from '@angular/core';
import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material'; import { ErrorStateMatcher, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms'; import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
@ -28,7 +27,10 @@ import {
} from '@shared/models/relation.models'; } from '@shared/models/relation.models';
import { EntityRelationService } from '@core/http/entity-relation.service'; import { EntityRelationService } from '@core/http/entity-relation.service';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { Observable, forkJoin } from 'rxjs'; import { forkJoin, Observable } from 'rxjs';
import { JsonObjectEditComponent } from '@app/shared/components/json-object-edit.component';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
export interface RelationDialogData { export interface RelationDialogData {
isAdd: boolean; isAdd: boolean;
@ -42,7 +44,7 @@ export interface RelationDialogData {
providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: RelationDialogComponent}],
styleUrls: ['./relation-dialog.component.scss'] styleUrls: ['./relation-dialog.component.scss']
}) })
export class RelationDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { export class RelationDialogComponent extends DialogComponent<RelationDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
relationFormGroup: FormGroup; relationFormGroup: FormGroup;
@ -50,15 +52,20 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
direction: EntitySearchDirection; direction: EntitySearchDirection;
entitySearchDirection = EntitySearchDirection; entitySearchDirection = EntitySearchDirection;
additionalInfo: FormControl;
@ViewChild('additionalInfoEdit', {static: true}) additionalInfoEdit: JsonObjectEditComponent;
submitted = false; submitted = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: RelationDialogData, @Inject(MAT_DIALOG_DATA) public data: RelationDialogData,
private entityRelationService: EntityRelationService, private entityRelationService: EntityRelationService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<RelationDialogComponent, boolean>, public dialogRef: MatDialogRef<RelationDialogComponent, boolean>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
this.isAdd = data.isAdd; this.isAdd = data.isAdd;
this.direction = data.direction; this.direction = data.direction;
} }
@ -68,14 +75,14 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]], type: [this.isAdd ? CONTAINS_TYPE : this.data.relation.type, [Validators.required]],
targetEntityIds: [this.isAdd ? null : targetEntityIds: [this.isAdd ? null :
[this.direction === EntitySearchDirection.FROM ? this.data.relation.to : this.data.relation.from], [this.direction === EntitySearchDirection.FROM ? this.data.relation.to : this.data.relation.from],
[Validators.required]], [Validators.required]]
additionalInfo: [this.data.relation.additionalInfo]
}); });
this.additionalInfo = new FormControl(this.data.relation.additionalInfo ? {...this.data.relation.additionalInfo} : null);
if (!this.isAdd) { if (!this.isAdd) {
this.relationFormGroup.get('type').disable(); this.relationFormGroup.get('type').disable();
this.relationFormGroup.get('targetEntityIds').disable(); this.relationFormGroup.get('targetEntityIds').disable();
} }
this.relationFormGroup.valueChanges.subscribe( this.additionalInfo.valueChanges.subscribe(
() => { () => {
this.submitted = false; this.submitted = false;
} }
@ -94,8 +101,8 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
save(): void { save(): void {
this.submitted = true; this.submitted = true;
if (this.relationFormGroup.valid) { this.additionalInfoEdit.validateOnSubmit();
const additionalInfo = this.relationFormGroup.get('additionalInfo').value; if (!this.relationFormGroup.invalid && !this.additionalInfo.invalid) {
if (this.isAdd) { if (this.isAdd) {
const tasks: Observable<EntityRelation>[] = []; const tasks: Observable<EntityRelation>[] = [];
const type: string = this.relationFormGroup.get('type').value; const type: string = this.relationFormGroup.get('type').value;
@ -103,7 +110,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
entityIds.forEach(entityId => { entityIds.forEach(entityId => {
const relation = { const relation = {
type, type,
additionalInfo, additionalInfo: this.additionalInfo.value,
typeGroup: RelationTypeGroup.COMMON typeGroup: RelationTypeGroup.COMMON
} as EntityRelation; } as EntityRelation;
if (this.direction === EntitySearchDirection.FROM) { if (this.direction === EntitySearchDirection.FROM) {
@ -122,7 +129,7 @@ export class RelationDialogComponent extends PageComponent implements OnInit, Er
); );
} else { } else {
const relation: EntityRelation = {...this.data.relation}; const relation: EntityRelation = {...this.data.relation};
relation.additionalInfo = additionalInfo; relation.additionalInfo = this.additionalInfo.value;
this.entityRelationService.saveRelation(relation).subscribe( this.entityRelationService.saveRelation(relation).subscribe(
() => { () => {
this.dialogRef.close(true); this.dialogRef.close(true);

8
ui-ngx/src/app/modules/home/dialogs/add-entities-to-customer-dialog.component.ts

@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs';
import {AssetService} from '@core/http/asset.service'; import {AssetService} from '@core/http/asset.service';
import {EntityViewService} from '@core/http/entity-view.service'; import {EntityViewService} from '@core/http/entity-view.service';
import {DashboardService} from '@core/http/dashboard.service'; import {DashboardService} from '@core/http/dashboard.service';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface AddEntitiesToCustomerDialogData { export interface AddEntitiesToCustomerDialogData {
customerId: string; customerId: string;
@ -38,7 +40,8 @@ export interface AddEntitiesToCustomerDialogData {
providers: [{provide: ErrorStateMatcher, useExisting: AddEntitiesToCustomerDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: AddEntitiesToCustomerDialogComponent}],
styleUrls: [] styleUrls: []
}) })
export class AddEntitiesToCustomerDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { export class AddEntitiesToCustomerDialogComponent extends
DialogComponent<AddEntitiesToCustomerDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
addEntitiesToCustomerFormGroup: FormGroup; addEntitiesToCustomerFormGroup: FormGroup;
@ -50,6 +53,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen
assignToCustomerText: string; assignToCustomerText: string;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData, @Inject(MAT_DIALOG_DATA) public data: AddEntitiesToCustomerDialogData,
private deviceService: DeviceService, private deviceService: DeviceService,
private assetService: AssetService, private assetService: AssetService,
@ -58,7 +62,7 @@ export class AddEntitiesToCustomerDialogComponent extends PageComponent implemen
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AddEntitiesToCustomerDialogComponent, boolean>, public dialogRef: MatDialogRef<AddEntitiesToCustomerDialogComponent, boolean>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
this.entityType = data.entityType; this.entityType = data.entityType;
} }

8
ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.ts

@ -26,6 +26,8 @@ import {EntityType} from '@shared/models/entity-type.models';
import {forkJoin, Observable} from 'rxjs'; import {forkJoin, Observable} from 'rxjs';
import {AssetService} from '@core/http/asset.service'; import {AssetService} from '@core/http/asset.service';
import {EntityViewService} from '@core/http/entity-view.service'; import {EntityViewService} from '@core/http/entity-view.service';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface AssignToCustomerDialogData { export interface AssignToCustomerDialogData {
entityIds: Array<EntityId>; entityIds: Array<EntityId>;
@ -38,7 +40,8 @@ export interface AssignToCustomerDialogData {
providers: [{provide: ErrorStateMatcher, useExisting: AssignToCustomerDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: AssignToCustomerDialogComponent}],
styleUrls: [] styleUrls: []
}) })
export class AssignToCustomerDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { export class AssignToCustomerDialogComponent extends
DialogComponent<AssignToCustomerDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
assignToCustomerFormGroup: FormGroup; assignToCustomerFormGroup: FormGroup;
@ -50,6 +53,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On
assignToCustomerText: string; assignToCustomerText: string;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData, @Inject(MAT_DIALOG_DATA) public data: AssignToCustomerDialogData,
private deviceService: DeviceService, private deviceService: DeviceService,
private assetService: AssetService, private assetService: AssetService,
@ -57,7 +61,7 @@ export class AssignToCustomerDialogComponent extends PageComponent implements On
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AssignToCustomerDialogComponent, boolean>, public dialogRef: MatDialogRef<AssignToCustomerDialogComponent, boolean>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {

4
ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html

@ -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>

4
ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html

@ -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.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table> <tb-audit-log-table [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.CUSTOMER" [customerId]="entity.id" detailsMode="true"></tb-audit-log-table>

7
ui-ngx/src/app/modules/home/pages/dashboard/make-dashboard-public-dialog.component.ts

@ -26,6 +26,8 @@ import {forkJoin, Observable} from 'rxjs';
import {DashboardInfo} from '@app/shared/models/dashboard.models'; import {DashboardInfo} from '@app/shared/models/dashboard.models';
import {ActionNotificationShow} from '@core/notification/notification.actions'; import {ActionNotificationShow} from '@core/notification/notification.actions';
import {TranslateService} from '@ngx-translate/core'; import {TranslateService} from '@ngx-translate/core';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface MakeDashboardPublicDialogData { export interface MakeDashboardPublicDialogData {
dashboard: DashboardInfo; dashboard: DashboardInfo;
@ -36,19 +38,20 @@ export interface MakeDashboardPublicDialogData {
templateUrl: './make-dashboard-public-dialog.component.html', templateUrl: './make-dashboard-public-dialog.component.html',
styleUrls: [] styleUrls: []
}) })
export class MakeDashboardPublicDialogComponent extends PageComponent implements OnInit { export class MakeDashboardPublicDialogComponent extends DialogComponent<MakeDashboardPublicDialogComponent> implements OnInit {
dashboard: DashboardInfo; dashboard: DashboardInfo;
publicLink: string; publicLink: string;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: MakeDashboardPublicDialogData, @Inject(MAT_DIALOG_DATA) public data: MakeDashboardPublicDialogData,
public translate: TranslateService, public translate: TranslateService,
private dashboardService: DashboardService, private dashboardService: DashboardService,
public dialogRef: MatDialogRef<MakeDashboardPublicDialogComponent>, public dialogRef: MatDialogRef<MakeDashboardPublicDialogComponent>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
this.dashboard = data.dashboard; this.dashboard = data.dashboard;
this.publicLink = dashboardService.getPublicDashboardLink(this.dashboard); this.publicLink = dashboardService.getPublicDashboardLink(this.dashboard);

8
ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.ts

@ -23,6 +23,8 @@ import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from '@
import {EntityType} from '@shared/models/entity-type.models'; import {EntityType} from '@shared/models/entity-type.models';
import {DashboardService} from '@core/http/dashboard.service'; import {DashboardService} from '@core/http/dashboard.service';
import {forkJoin, Observable} from 'rxjs'; import {forkJoin, Observable} from 'rxjs';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign'; export type ManageDashboardCustomersActionType = 'assign' | 'manage' | 'unassign';
@ -38,7 +40,8 @@ export interface ManageDashboardCustomersDialogData {
providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardCustomersDialogComponent}],
styleUrls: [] styleUrls: []
}) })
export class ManageDashboardCustomersDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { export class ManageDashboardCustomersDialogComponent extends
DialogComponent<ManageDashboardCustomersDialogComponent, boolean> implements OnInit, ErrorStateMatcher {
dashboardCustomersFormGroup: FormGroup; dashboardCustomersFormGroup: FormGroup;
@ -53,12 +56,13 @@ export class ManageDashboardCustomersDialogComponent extends PageComponent imple
assignedCustomersIds: string[]; assignedCustomersIds: string[];
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData, @Inject(MAT_DIALOG_DATA) public data: ManageDashboardCustomersDialogData,
private dashboardService: DashboardService, private dashboardService: DashboardService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<ManageDashboardCustomersDialogComponent, boolean>, public dialogRef: MatDialogRef<ManageDashboardCustomersDialogComponent, boolean>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
this.assignedCustomersIds = data.assignedCustomersIds || []; this.assignedCustomersIds = data.assignedCustomersIds || [];
switch (data.actionType) { switch (data.actionType) {

8
ui-ngx/src/app/modules/home/pages/device/device-credentials-dialog.component.ts

@ -25,6 +25,8 @@ import { TranslateService } from '@ngx-translate/core';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import {DeviceService} from '@core/http/device.service'; import {DeviceService} from '@core/http/device.service';
import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models'; import {DeviceCredentials, DeviceCredentialsType, credentialTypeNames} from '@shared/models/device.models';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface DeviceCredentialsDialogData { export interface DeviceCredentialsDialogData {
isReadOnly: boolean; isReadOnly: boolean;
@ -37,7 +39,8 @@ export interface DeviceCredentialsDialogData {
providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}], providers: [{provide: ErrorStateMatcher, useExisting: DeviceCredentialsDialogComponent}],
styleUrls: [] styleUrls: []
}) })
export class DeviceCredentialsDialogComponent extends PageComponent implements OnInit, ErrorStateMatcher { export class DeviceCredentialsDialogComponent extends
DialogComponent<DeviceCredentialsDialogComponent, DeviceCredentials> implements OnInit, ErrorStateMatcher {
deviceCredentialsFormGroup: FormGroup; deviceCredentialsFormGroup: FormGroup;
@ -54,12 +57,13 @@ export class DeviceCredentialsDialogComponent extends PageComponent implements O
credentialTypeNamesMap = credentialTypeNames; credentialTypeNamesMap = credentialTypeNames;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData, @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogData,
private deviceService: DeviceService, private deviceService: DeviceService,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, @SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>, public dialogRef: MatDialogRef<DeviceCredentialsDialogComponent, DeviceCredentials>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
this.isReadOnly = data.isReadOnly; this.isReadOnly = data.isReadOnly;
} }

4
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html

@ -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>

7
ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.ts

@ -23,22 +23,25 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { AuthService } from '@core/auth/auth.service'; import { AuthService } from '@core/auth/auth.service';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
@Component({ @Component({
selector: 'tb-change-password-dialog', selector: 'tb-change-password-dialog',
templateUrl: './change-password-dialog.component.html', templateUrl: './change-password-dialog.component.html',
styleUrls: ['./change-password-dialog.component.scss'] styleUrls: ['./change-password-dialog.component.scss']
}) })
export class ChangePasswordDialogComponent extends PageComponent implements OnInit { export class ChangePasswordDialogComponent extends DialogComponent<ChangePasswordDialogComponent> implements OnInit {
changePassword: FormGroup; changePassword: FormGroup;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
private translate: TranslateService, private translate: TranslateService,
private authService: AuthService, private authService: AuthService,
public dialogRef: MatDialogRef<ChangePasswordDialogComponent>, public dialogRef: MatDialogRef<ChangePasswordDialogComponent>,
public fb: FormBuilder) { public fb: FormBuilder) {
super(store); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {

4
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html

@ -23,6 +23,10 @@
[tenantId]="entity.tenantId.id" [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>

4
ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html

@ -20,3 +20,7 @@
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid" <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid"
[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>

7
ui-ngx/src/app/modules/home/pages/user/activation-link-dialog.component.ts

@ -21,6 +21,8 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface ActivationLinkDialogData { export interface ActivationLinkDialogData {
activationLink: string; activationLink: string;
@ -30,15 +32,16 @@ export interface ActivationLinkDialogData {
selector: 'tb-activation-link-dialog', selector: 'tb-activation-link-dialog',
templateUrl: './activation-link-dialog.component.html' templateUrl: './activation-link-dialog.component.html'
}) })
export class ActivationLinkDialogComponent extends PageComponent implements OnInit { export class ActivationLinkDialogComponent extends DialogComponent<ActivationLinkDialogComponent, void> implements OnInit {
activationLink: string; activationLink: string;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: ActivationLinkDialogData, @Inject(MAT_DIALOG_DATA) public data: ActivationLinkDialogData,
public dialogRef: MatDialogRef<ActivationLinkDialogComponent, void>, public dialogRef: MatDialogRef<ActivationLinkDialogComponent, void>,
private translate: TranslateService) { private translate: TranslateService) {
super(store); super(store, router, dialogRef);
this.activationLink = this.data.activationLink; this.activationLink = this.data.activationLink;
} }

7
ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.ts

@ -31,6 +31,8 @@ import {
ActivationLinkDialogData ActivationLinkDialogData
} from '@modules/home/pages/user/activation-link-dialog.component'; } from '@modules/home/pages/user/activation-link-dialog.component';
import {TenantId} from '@app/shared/models/id/tenant-id'; import {TenantId} from '@app/shared/models/id/tenant-id';
import { DialogComponent } from '@shared/components/dialog.component';
import { Router } from '@angular/router';
export interface AddUserDialogData { export interface AddUserDialogData {
tenantId: string; tenantId: string;
@ -42,7 +44,7 @@ export interface AddUserDialogData {
selector: 'tb-add-user-dialog', selector: 'tb-add-user-dialog',
templateUrl: './add-user-dialog.component.html' templateUrl: './add-user-dialog.component.html'
}) })
export class AddUserDialogComponent extends PageComponent implements OnInit { export class AddUserDialogComponent extends DialogComponent<AddUserDialogComponent, User> implements OnInit {
detailsForm: NgForm; detailsForm: NgForm;
user: User; user: User;
@ -56,11 +58,12 @@ export class AddUserDialogComponent extends PageComponent implements OnInit {
@ViewChild(UserComponent, {static: true}) userComponent: UserComponent; @ViewChild(UserComponent, {static: true}) userComponent: UserComponent;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: AddUserDialogData, @Inject(MAT_DIALOG_DATA) public data: AddUserDialogData,
public dialogRef: MatDialogRef<AddUserDialogComponent, User>, public dialogRef: MatDialogRef<AddUserDialogComponent, User>,
private userService: UserService, private userService: UserService,
private dialog: MatDialog) { private dialog: MatDialog) {
super(store); super(store, router, dialogRef);
} }
ngOnInit(): void { ngOnInit(): void {

51
ui-ngx/src/app/shared/components/dialog.component.ts

@ -0,0 +1,51 @@
///
/// 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 { OnDestroy } from '@angular/core';
import { PageComponent } from './page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { MatDialogRef } from '@angular/material/dialog';
import { NavigationStart, Router, RouterEvent } from '@angular/router';
import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
export abstract class DialogComponent<T, R = any> extends PageComponent implements OnDestroy {
routerSubscription: Subscription;
protected constructor(protected store: Store<AppState>,
protected router: Router,
protected dialogRef: MatDialogRef<T, R>) {
super(store);
this.routerSubscription = this.router.events
.pipe(
filter((event: RouterEvent) => event instanceof NavigationStart),
filter(() => !!this.dialogRef)
)
.subscribe(() => {
this.dialogRef.close();
});
}
ngOnDestroy(): void {
console.log('Dialog destroy called');
super.ngOnDestroy();
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();
}
}
}

1
ui-ngx/src/app/shared/components/entity/entity-list.component.html

@ -28,6 +28,7 @@
<input matInput type="text" placeholder="{{ !disabled ? ('entity.entity-list' | translate) : '' }}" <input matInput type="text" placeholder="{{ !disabled ? ('entity.entity-list' | translate) : '' }}"
style="max-width: 200px;" style="max-width: 200px;"
#entityInput #entityInput
(focusin)="onFocus()"
formControlName="entity" formControlName="entity"
matAutocompleteOrigin matAutocompleteOrigin
#origin="matAutocompleteOrigin" #origin="matAutocompleteOrigin"

26
ui-ngx/src/app/shared/components/entity/entity-list.component.ts

@ -99,6 +99,8 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
private searchText = ''; private searchText = '';
private dirty = false;
private propagateChange = (v: any) => { }; private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>, constructor(private store: Store<AppState>,
@ -126,7 +128,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
ngOnInit() { ngOnInit() {
this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges
.pipe( .pipe(
startWith<string | BaseData<EntityId>>(''), // startWith<string | BaseData<EntityId>>(''),
tap((value) => { tap((value) => {
if (value && typeof value !== 'string') { if (value && typeof value !== 'string') {
this.add(value); this.add(value);
@ -145,12 +147,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
} }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
const emitEvent = this.disabled !== isDisabled;
this.disabled = isDisabled; this.disabled = isDisabled;
if (isDisabled) { if (isDisabled) {
this.entityListFormGroup.disable({emitEvent}); this.entityListFormGroup.disable({emitEvent: false});
} else { } else {
this.entityListFormGroup.enable({emitEvent}); this.entityListFormGroup.enable({emitEvent: false});
} }
} }
@ -169,14 +170,19 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.entityListFormGroup.get('entities').setValue(this.entities); this.entityListFormGroup.get('entities').setValue(this.entities);
this.modelValue = null; this.modelValue = null;
} }
this.dirty = true;
} }
reset() { reset() {
this.entities = []; this.entities = [];
this.entityListFormGroup.get('entities').setValue(this.entities); this.entityListFormGroup.get('entities').setValue(this.entities);
this.modelValue = null; this.modelValue = null;
this.entityListFormGroup.get('entity').patchValue('', {emitEvent: true}); if (this.entityInput) {
this.entityInput.nativeElement.value = '';
}
this.entityListFormGroup.get('entity').patchValue('', {emitEvent: false});
this.propagateChange(this.modelValue); this.propagateChange(this.modelValue);
this.dirty = true;
} }
add(entity: BaseData<EntityId>): void { add(entity: BaseData<EntityId>): void {
@ -212,12 +218,18 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> { fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
this.searchText = searchText; this.searchText = searchText;
return this.disabled ? of([]) : return this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText,
this.entityService.getEntitiesByNameFilter(this.entityTypeValue, searchText,
50, '', false, true).pipe( 50, '', false, true).pipe(
map((data) => data ? data : [])); map((data) => data ? data : []));
} }
onFocus() {
if (this.dirty) {
this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
clear(value: string = '') { clear(value: string = '') {
this.entityInput.nativeElement.value = value; this.entityInput.nativeElement.value = value;
this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true});

21
ui-ngx/src/app/shared/components/fullscreen.directive.ts

@ -54,13 +54,13 @@ export class FullscreenDirective {
constructor(public elementRef: ElementRef, constructor(public elementRef: ElementRef,
private viewContainerRef: ViewContainerRef, private viewContainerRef: ViewContainerRef,
private overlay: Overlay) { private overlay: Overlay) {
} }
enterFullscreen() { enterFullscreen() {
this.parentElement = this.elementRef.nativeElement.parentElement; const targetElement = this.elementRef;
this.parentElement.removeChild(this.elementRef.nativeElement); this.parentElement = targetElement.nativeElement.parentElement;
this.elementRef.nativeElement.classList.add('tb-fullscreen'); this.parentElement.removeChild(targetElement.nativeElement);
targetElement.nativeElement.classList.add('tb-fullscreen');
const position = this.overlay.position(); const position = this.overlay.position();
const config = new OverlayConfig({ const config = new OverlayConfig({
hasBackdrop: false, hasBackdrop: false,
@ -73,21 +73,24 @@ export class FullscreenDirective {
this.overlayRef = this.overlay.create(config); this.overlayRef = this.overlay.create(config);
this.overlayRef.attach(new EmptyPortal()); this.overlayRef.attach(new EmptyPortal());
this.overlayRef.overlayElement.append( this.elementRef.nativeElement ); this.overlayRef.overlayElement.append( targetElement.nativeElement );
this.fullscreenChanged.emit(true); this.fullscreenChanged.emit(true);
} }
exitFullscreen() { exitFullscreen() {
const targetElement = this.elementRef;
if (this.parentElement) { if (this.parentElement) {
this.overlayRef.overlayElement.removeChild( this.elementRef.nativeElement ); this.overlayRef.overlayElement.removeChild( targetElement.nativeElement );
this.parentElement.append( this.elementRef.nativeElement ); this.parentElement.append(targetElement.nativeElement);
this.parentElement = null; this.parentElement = null;
} }
this.elementRef.nativeElement.classList.remove('tb-fullscreen'); targetElement.nativeElement.classList.remove('tb-fullscreen');
if (this.elementRef !== targetElement) {
this.elementRef.nativeElement.classList.remove('tb-fullscreen');
}
this.overlayRef.dispose(); this.overlayRef.dispose();
this.fullscreenChanged.emit(false); this.fullscreenChanged.emit(false);
} }
} }
class EmptyPortal extends ComponentPortal<TbAnchorComponent> { class EmptyPortal extends ComponentPortal<TbAnchorComponent> {

6
ui-ngx/src/app/shared/components/json-object-edit.component.html

@ -16,7 +16,8 @@
--> -->
<div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}" <div style="background: #fff;" [ngClass]="{'fill-height': fillHeight}"
tb-fullscreen [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column"> tb-fullscreen
[fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start center"> <div fxLayout="row" fxLayoutAlign="start center">
<label class="tb-title no-padding" <label class="tb-title no-padding"
ng-class="{'tb-required': required, ng-class="{'tb-required': required,
@ -29,7 +30,8 @@
<mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon> <mat-icon class="material-icons">{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
</button> </button>
</div> </div>
<div fxFlex="0%" id="tb-json-panel" class="tb-json-object-panel" fxLayout="column"> <div fxFlex="0%" id="tb-json-panel" tb-toast toastTarget="jsonObjectEditor"
class="tb-json-object-panel" fxLayout="column">
<div fxFlex #jsonEditor id="tb-json-input" [ngClass]="{'fill-height': fillHeight}"></div> <div fxFlex #jsonEditor id="tb-json-input" [ngClass]="{'fill-height': fillHeight}"></div>
</div> </div>
</div> </div>

23
ui-ngx/src/app/shared/components/json-object-edit.component.scss

@ -35,21 +35,20 @@
.fill-height { .fill-height {
height: 100%; height: 100%;
} }
}
.tb-json-object-panel { .tb-json-object-panel {
height: 100%; height: 100%;
margin-left: 15px; margin-left: 15px;
border: 1px solid #c0c0c0; border: 1px solid #c0c0c0;
#tb-json-input { #tb-json-input {
width: 100%; width: 100%;
min-width: 200px; min-width: 200px;
height: 100%; height: 100%;
&:not(.fill-height) { &:not(.fill-height) {
min-height: 200px; min-height: 200px;
}
} }
} }
} }

52
ui-ngx/src/app/shared/components/json-object-edit.component.ts

@ -26,6 +26,9 @@ import {
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormControl, Validator, NG_VALIDATORS } from '@angular/forms';
import * as ace from 'ace-builds'; import * as ace from 'ace-builds';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
@Component({ @Component({
selector: 'tb-json-object-edit', selector: 'tb-json-object-edit',
@ -83,9 +86,14 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
objectValid: boolean; objectValid: boolean;
validationError: string;
errorShowed = false;
private propagateChange = null; private propagateChange = null;
constructor() { constructor(public elementRef: ElementRef,
protected store: Store<AppState>) {
} }
ngOnInit(): void { ngOnInit(): void {
@ -109,6 +117,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
this.jsonEditor.session.setUseWrapMode(false); this.jsonEditor.session.setUseWrapMode(false);
this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1); this.jsonEditor.setValue(this.contentValue ? this.contentValue : '', -1);
this.jsonEditor.on('change', () => { this.jsonEditor.on('change', () => {
this.cleanupJsonErrors();
this.updateView(); this.updateView();
}); });
} }
@ -132,6 +141,33 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
}; };
} }
validateOnSubmit(): void {
if (!this.readonly) {
this.cleanupJsonErrors();
if (!this.objectValid) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.validationError,
type: 'error',
target: 'jsonObjectEditor',
verticalPosition: 'bottom',
horizontalPosition: 'left'
}));
this.errorShowed = true;
}
}
}
cleanupJsonErrors(): void {
if (this.errorShowed) {
this.store.dispatch(new ActionNotificationHide(
{
target: 'jsonObjectEditor'
}));
this.errorShowed = false;
}
}
writeValue(value: any): void { writeValue(value: any): void {
this.modelValue = value; this.modelValue = value;
this.contentValue = ''; this.contentValue = '';
@ -142,6 +178,7 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
this.objectValid = true; this.objectValid = true;
} else { } else {
this.objectValid = !this.required; this.objectValid = !this.required;
this.validationError = 'Json object is required.';
} }
} catch (e) { } catch (e) {
// //
@ -161,9 +198,20 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va
try { try {
data = JSON.parse(this.contentValue); data = JSON.parse(this.contentValue);
this.objectValid = true; this.objectValid = true;
} catch (ex) {} this.validationError = '';
} catch (ex) {
let errorInfo = 'Error:';
if (ex.name) {
errorInfo += ' ' + ex.name + ':';
}
if (ex.message) {
errorInfo += ' ' + ex.message;
}
this.validationError = errorInfo;
}
} else { } else {
this.objectValid = !this.required; this.objectValid = !this.required;
this.validationError = this.required ? 'Json object is required.' : '';
} }
this.propagateChange(data); this.propagateChange(data);
} }

4
ui-ngx/src/app/shared/components/page.component.ts

@ -19,7 +19,7 @@ import { select, Store } from '@ngrx/store';
import { AppState } from '../../core/core.state'; import { AppState } from '../../core/core.state';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { selectIsLoading } from '../../core/interceptors/load.selectors'; import { selectIsLoading } from '../../core/interceptors/load.selectors';
import { delay } from 'rxjs/operators'; import { delay, share } from 'rxjs/operators';
import { AbstractControl } from '@angular/forms'; import { AbstractControl } from '@angular/forms';
export abstract class PageComponent implements OnDestroy { export abstract class PageComponent implements OnDestroy {
@ -29,7 +29,7 @@ export abstract class PageComponent implements OnDestroy {
disabledOnLoadFormControls: Array<AbstractControl> = []; disabledOnLoadFormControls: Array<AbstractControl> = [];
protected constructor(protected store: Store<AppState>) { protected constructor(protected store: Store<AppState>) {
this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), delay(100)); this.isLoading$ = this.store.pipe(delay(0), select(selectIsLoading), share());
} }
protected registerDisableOnLoadFormControl(control: AbstractControl) { protected registerDisableOnLoadFormControl(control: AbstractControl) {

15
ui-ngx/src/app/shared/components/toast.directive.ts

@ -45,6 +45,7 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
toastTarget = 'root'; toastTarget = 'root';
private notificationSubscription: Subscription = null; private notificationSubscription: Subscription = null;
private hideNotificationSubscription: Subscription = null;
constructor(public elementRef: ElementRef, constructor(public elementRef: ElementRef,
public viewContainerRef: ViewContainerRef, public viewContainerRef: ViewContainerRef,
@ -78,12 +79,26 @@ export class ToastDirective implements AfterViewInit, OnDestroy {
} }
} }
); );
this.hideNotificationSubscription = this.notificationService.getHideNotification().subscribe(
(hideNotification) => {
if (hideNotification) {
const target = hideNotification.target || 'root';
if (this.toastTarget === target) {
this.snackBar.dismiss();
}
}
}
);
} }
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.notificationSubscription) { if (this.notificationSubscription) {
this.notificationSubscription.unsubscribe(); this.notificationSubscription.unsubscribe();
} }
if (this.hideNotificationSubscription) {
this.hideNotificationSubscription.unsubscribe();
}
} }
} }

Loading…
Cancel
Save