Commit f39d6638273ba5f49bed00e123e0889d3092a066

Authored by Igor Kulikov
1 parent 85b31df4

UI: Tenant Profiles

Showing 21 changed files with 810 additions and 31 deletions
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { HttpClient } from '@angular/common/http';
  19 +import { PageLink } from '@shared/models/page/page-link';
  20 +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
  21 +import { Observable } from 'rxjs';
  22 +import { PageData } from '@shared/models/page/page-data';
  23 +import { TenantProfile } from '@shared/models/tenant.model';
  24 +import { EntityInfoData } from '@shared/models/entity.models';
  25 +
  26 +@Injectable({
  27 + providedIn: 'root'
  28 +})
  29 +export class TenantProfileService {
  30 +
  31 + constructor(
  32 + private http: HttpClient
  33 + ) { }
  34 +
  35 + public getTenantProfiles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<TenantProfile>> {
  36 + return this.http.get<PageData<TenantProfile>>(`/api/tenantProfiles${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
  37 + }
  38 +
  39 + public getTenantProfile(tenantProfileId: string, config?: RequestConfig): Observable<TenantProfile> {
  40 + return this.http.get<TenantProfile>(`/api/tenantProfile/${tenantProfileId}`, defaultHttpOptionsFromConfig(config));
  41 + }
  42 +
  43 + public saveTenantProfile(tenantProfile: TenantProfile, config?: RequestConfig): Observable<TenantProfile> {
  44 + return this.http.post<TenantProfile>('/api/tenantProfile', tenantProfile, defaultHttpOptionsFromConfig(config));
  45 + }
  46 +
  47 + public deleteTenantProfile(tenantProfileId: string, config?: RequestConfig) {
  48 + return this.http.delete(`/api/tenantProfile/${tenantProfileId}`, defaultHttpOptionsFromConfig(config));
  49 + }
  50 +
  51 + public setDefaultTenantProfile(tenantProfileId: string, config?: RequestConfig): Observable<TenantProfile> {
  52 + return this.http.post<TenantProfile>(`/api/tenantProfile/${tenantProfileId}/default`, defaultHttpOptionsFromConfig(config));
  53 + }
  54 +
  55 + public getDefaultTenantProfileInfo(config?: RequestConfig): Observable<EntityInfoData> {
  56 + return this.http.get<EntityInfoData>('/api/tenantProfileInfo/default', defaultHttpOptionsFromConfig(config));
  57 + }
  58 +
  59 + public getTenantProfileInfo(tenantProfileId: string, config?: RequestConfig): Observable<EntityInfoData> {
  60 + return this.http.get<EntityInfoData>(`/api/tenantProfileInfo/${tenantProfileId}`, defaultHttpOptionsFromConfig(config));
  61 + }
  62 +
  63 + public getTenantProfileInfos(pageLink: PageLink, config?: RequestConfig): Observable<PageData<EntityInfoData>> {
  64 + return this.http.get<PageData<EntityInfoData>>(`/api/tenantProfileInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
  65 + }
  66 +
  67 +}
@@ -249,7 +249,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { @@ -249,7 +249,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
249 } else { 249 } else {
250 this.activeRequests--; 250 this.activeRequests--;
251 } 251 }
252 - if (this.activeRequests === 1) { 252 + if (this.activeRequests === 1 && isLoading) {
253 this.store.dispatch(new ActionLoadStart()); 253 this.store.dispatch(new ActionLoadStart());
254 } else if (this.activeRequests === 0) { 254 } else if (this.activeRequests === 0) {
255 this.store.dispatch(new ActionLoadFinish()); 255 this.store.dispatch(new ActionLoadFinish());
@@ -86,6 +86,13 @@ export class MenuService { @@ -86,6 +86,13 @@ export class MenuService {
86 icon: 'supervisor_account' 86 icon: 'supervisor_account'
87 }, 87 },
88 { 88 {
  89 + name: 'tenant-profile.tenant-profiles',
  90 + type: 'link',
  91 + path: '/tenantProfiles',
  92 + icon: 'mdi:alpha-t-box',
  93 + isMdiIcon: true
  94 + },
  95 + {
89 name: 'widget.widget-library', 96 name: 'widget.widget-library',
90 type: 'link', 97 type: 'link',
91 path: '/widgets-bundles', 98 path: '/widgets-bundles',
@@ -132,7 +139,13 @@ export class MenuService { @@ -132,7 +139,13 @@ export class MenuService {
132 name: 'tenant.tenants', 139 name: 'tenant.tenants',
133 icon: 'supervisor_account', 140 icon: 'supervisor_account',
134 path: '/tenants' 141 path: '/tenants'
135 - } 142 + },
  143 + {
  144 + name: 'tenant-profile.tenant-profiles',
  145 + icon: 'mdi:alpha-t-box',
  146 + isMdiIcon: true,
  147 + path: '/tenantProfiles'
  148 + },
136 ] 149 ]
137 }, 150 },
138 { 151 {
@@ -36,9 +36,9 @@ import { MatDialog } from '@angular/material/dialog'; @@ -36,9 +36,9 @@ import { MatDialog } from '@angular/material/dialog';
36 import { MatPaginator } from '@angular/material/paginator'; 36 import { MatPaginator } from '@angular/material/paginator';
37 import { MatSort } from '@angular/material/sort'; 37 import { MatSort } from '@angular/material/sort';
38 import { EntitiesDataSource } from '@home/models/datasource/entity-datasource'; 38 import { EntitiesDataSource } from '@home/models/datasource/entity-datasource';
39 -import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; 39 +import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
40 import { Direction, SortOrder } from '@shared/models/page/sort-order'; 40 import { Direction, SortOrder } from '@shared/models/page/sort-order';
41 -import { forkJoin, fromEvent, merge, Observable, Subscription } from 'rxjs'; 41 +import { forkJoin, fromEvent, merge, Observable, of, Subscription } from 'rxjs';
42 import { TranslateService } from '@ngx-translate/core'; 42 import { TranslateService } from '@ngx-translate/core';
43 import { BaseData, HasId } from '@shared/models/base-data'; 43 import { BaseData, HasId } from '@shared/models/base-data';
44 import { ActivatedRoute } from '@angular/router'; 44 import { ActivatedRoute } from '@angular/router';
@@ -59,6 +59,7 @@ import { DAY, historyInterval, HistoryWindowType, Timewindow } from '@shared/mod @@ -59,6 +59,7 @@ import { DAY, historyInterval, HistoryWindowType, Timewindow } from '@shared/mod
59 import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 59 import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
60 import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; 60 import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
61 import { isDefined, isUndefined } from '@core/utils'; 61 import { isDefined, isUndefined } from '@core/utils';
  62 +import { HasUUID } from '../../../../shared/models/id/has-uuid';
62 63
63 @Component({ 64 @Component({
64 selector: 'tb-entities-table', 65 selector: 'tb-entities-table',
@@ -401,16 +402,19 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn @@ -401,16 +402,19 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
401 true 402 true
402 ).subscribe((result) => { 403 ).subscribe((result) => {
403 if (result) { 404 if (result) {
404 - const tasks: Observable<any>[] = []; 405 + const tasks: Observable<HasUUID>[] = [];
405 entities.forEach((entity) => { 406 entities.forEach((entity) => {
406 if (this.entitiesTableConfig.deleteEnabled(entity)) { 407 if (this.entitiesTableConfig.deleteEnabled(entity)) {
407 - tasks.push(this.entitiesTableConfig.deleteEntity(entity.id)); 408 + tasks.push(this.entitiesTableConfig.deleteEntity(entity.id).pipe(
  409 + map(() => entity.id),
  410 + catchError(() => of(null)
  411 + )));
408 } 412 }
409 }); 413 });
410 forkJoin(tasks).subscribe( 414 forkJoin(tasks).subscribe(
411 - () => { 415 + (ids) => {
412 this.updateData(); 416 this.updateData();
413 - this.entitiesTableConfig.entitiesDeleted(entities.map((e) => e.id)); 417 + this.entitiesTableConfig.entitiesDeleted(ids.filter(id => id !== null));
414 } 418 }
415 ); 419 );
416 } 420 }
@@ -84,6 +84,8 @@ import { UserFilterDialogComponent } from '@home/components/filter/user-filter-d @@ -84,6 +84,8 @@ import { UserFilterDialogComponent } from '@home/components/filter/user-filter-d
84 import { FilterUserInfoComponent } from './filter/filter-user-info.component'; 84 import { FilterUserInfoComponent } from './filter/filter-user-info.component';
85 import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component'; 85 import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component';
86 import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component'; 86 import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component';
  87 +import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component';
  88 +import { TenantProfileComponent } from './profile/tenant-profile.component';
87 89
88 @NgModule({ 90 @NgModule({
89 declarations: 91 declarations:
@@ -150,7 +152,9 @@ import { FilterPredicateValueComponent } from './filter/filter-predicate-value.c @@ -150,7 +152,9 @@ import { FilterPredicateValueComponent } from './filter/filter-predicate-value.c
150 UserFilterDialogComponent, 152 UserFilterDialogComponent,
151 FilterUserInfoComponent, 153 FilterUserInfoComponent,
152 FilterUserInfoDialogComponent, 154 FilterUserInfoDialogComponent,
153 - FilterPredicateValueComponent 155 + FilterPredicateValueComponent,
  156 + TenantProfileAutocompleteComponent,
  157 + TenantProfileComponent
154 ], 158 ],
155 imports: [ 159 imports: [
156 CommonModule, 160 CommonModule,
@@ -206,7 +210,9 @@ import { FilterPredicateValueComponent } from './filter/filter-predicate-value.c @@ -206,7 +210,9 @@ import { FilterPredicateValueComponent } from './filter/filter-predicate-value.c
206 FiltersDialogComponent, 210 FiltersDialogComponent,
207 FilterSelectComponent, 211 FilterSelectComponent,
208 FiltersEditComponent, 212 FiltersEditComponent,
209 - UserFilterDialogComponent 213 + UserFilterDialogComponent,
  214 + TenantProfileAutocompleteComponent,
  215 + TenantProfileComponent
210 ], 216 ],
211 providers: [ 217 providers: [
212 WidgetComponentService, 218 WidgetComponentService,
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<mat-form-field [formGroup]="selectTenantProfileFormGroup" class="mat-block">
  19 + <input matInput type="text" placeholder="{{ 'tenant-profile.tenant-profile' | translate }}"
  20 + #tenantProfileInput
  21 + formControlName="tenantProfile"
  22 + [required]="required"
  23 + [matAutocomplete]="tenantProfileAutocomplete">
  24 + <button *ngIf="selectTenantProfileFormGroup.get('tenantProfile').value && !disabled"
  25 + type="button"
  26 + matSuffix mat-button mat-icon-button aria-label="Clear"
  27 + (click)="clear()">
  28 + <mat-icon class="material-icons">close</mat-icon>
  29 + </button>
  30 + <mat-autocomplete
  31 + class="tb-autocomplete"
  32 + #tenantProfileAutocomplete="matAutocomplete"
  33 + [displayWith]="displayTenantProfileFn">
  34 + <mat-option *ngFor="let tenantProfile of filteredTenantProfiles | async" [value]="tenantProfile">
  35 + <span [innerHTML]="tenantProfile.name | highlight:searchText"></span>
  36 + </mat-option>
  37 + <mat-option *ngIf="!(filteredTenantProfiles | async)?.length" [value]="null">
  38 + <span>
  39 + {{ translate.get('tenant-profile.no-tenant-profiles-matching', {entity: searchText}) | async }}
  40 + </span>
  41 + </mat-option>
  42 + </mat-autocomplete>
  43 + <mat-error *ngIf="selectTenantProfileFormGroup.get('tenantProfile').hasError('required')">
  44 + {{ 'tenant-profile.tenant-profile-required' | translate }}
  45 + </mat-error>
  46 +</mat-form-field>
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
  18 +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
  19 +import { Observable } from 'rxjs';
  20 +import { PageLink } from '@shared/models/page/page-link';
  21 +import { Direction } from '@shared/models/page/sort-order';
  22 +import { map, mergeMap, startWith, tap } from 'rxjs/operators';
  23 +import { Store } from '@ngrx/store';
  24 +import { AppState } from '@app/core/core.state';
  25 +import { TranslateService } from '@ngx-translate/core';
  26 +import { coerceBooleanProperty } from '@angular/cdk/coercion';
  27 +import { TenantProfileId } from '@shared/models/id/tenant-profile-id';
  28 +import { EntityInfoData } from '@shared/models/entity.models';
  29 +import { TenantProfileService } from '@core/http/tenant-profile.service';
  30 +import { entityIdEquals } from '../../../../shared/models/id/entity-id';
  31 +
  32 +@Component({
  33 + selector: 'tb-tenant-profile-autocomplete',
  34 + templateUrl: './tenant-profile-autocomplete.component.html',
  35 + styleUrls: [],
  36 + providers: [{
  37 + provide: NG_VALUE_ACCESSOR,
  38 + useExisting: forwardRef(() => TenantProfileAutocompleteComponent),
  39 + multi: true
  40 + }]
  41 +})
  42 +export class TenantProfileAutocompleteComponent implements ControlValueAccessor, OnInit {
  43 +
  44 + selectTenantProfileFormGroup: FormGroup;
  45 +
  46 + modelValue: TenantProfileId | null;
  47 +
  48 + @Input()
  49 + selectDefaultProfile = false;
  50 +
  51 + private requiredValue: boolean;
  52 + get required(): boolean {
  53 + return this.requiredValue;
  54 + }
  55 + @Input()
  56 + set required(value: boolean) {
  57 + this.requiredValue = coerceBooleanProperty(value);
  58 + }
  59 +
  60 + @Input()
  61 + disabled: boolean;
  62 +
  63 + @ViewChild('tenantProfileInput', {static: true}) tenantProfileInput: ElementRef;
  64 +
  65 + filteredTenantProfiles: Observable<Array<EntityInfoData>>;
  66 +
  67 + searchText = '';
  68 +
  69 + private propagateChange = (v: any) => { };
  70 +
  71 + constructor(private store: Store<AppState>,
  72 + public translate: TranslateService,
  73 + private tenantProfileService: TenantProfileService,
  74 + private fb: FormBuilder) {
  75 + this.selectTenantProfileFormGroup = this.fb.group({
  76 + tenantProfile: [null]
  77 + });
  78 + }
  79 +
  80 + registerOnChange(fn: any): void {
  81 + this.propagateChange = fn;
  82 + }
  83 +
  84 + registerOnTouched(fn: any): void {
  85 + }
  86 +
  87 + ngOnInit() {
  88 + this.filteredTenantProfiles = this.selectTenantProfileFormGroup.get('tenantProfile').valueChanges
  89 + .pipe(
  90 + tap((value: EntityInfoData | string) => {
  91 + let modelValue: TenantProfileId | null;
  92 + if (typeof value === 'string' || !value) {
  93 + modelValue = null;
  94 + } else {
  95 + modelValue = new TenantProfileId(value.id.id);
  96 + }
  97 + this.updateView(modelValue);
  98 + }),
  99 + startWith<string | EntityInfoData>(''),
  100 + map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
  101 + mergeMap(name => this.fetchTenantProfiles(name) )
  102 + );
  103 + }
  104 +
  105 + selectDefaultTenantProfileIfNeeded(): void {
  106 + if (this.selectDefaultProfile && !this.modelValue) {
  107 + this.tenantProfileService.getDefaultTenantProfileInfo().subscribe(
  108 + (profile) => {
  109 + if (profile) {
  110 + this.modelValue = new TenantProfileId(profile.id.id);
  111 + this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: false});
  112 + this.propagateChange(this.modelValue);
  113 + }
  114 + }
  115 + );
  116 + }
  117 + }
  118 +
  119 + setDisabledState(isDisabled: boolean): void {
  120 + this.disabled = isDisabled;
  121 + }
  122 +
  123 + writeValue(value: TenantProfileId | null): void {
  124 + this.searchText = '';
  125 + if (value != null) {
  126 + this.tenantProfileService.getTenantProfileInfo(value.id).subscribe(
  127 + (profile) => {
  128 + this.modelValue = new TenantProfileId(profile.id.id);
  129 + this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: true});
  130 + }
  131 + );
  132 + } else {
  133 + this.modelValue = null;
  134 + this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(null, {emitEvent: true});
  135 + this.selectDefaultTenantProfileIfNeeded();
  136 + }
  137 + }
  138 +
  139 + updateView(value: TenantProfileId | null) {
  140 + if (!entityIdEquals(this.modelValue, value)) {
  141 + this.modelValue = value;
  142 + this.propagateChange(this.modelValue);
  143 + }
  144 + }
  145 +
  146 + displayTenantProfileFn(profile?: EntityInfoData): string | undefined {
  147 + return profile ? profile.name : undefined;
  148 + }
  149 +
  150 + fetchTenantProfiles(searchText?: string): Observable<Array<EntityInfoData>> {
  151 + this.searchText = searchText;
  152 + const pageLink = new PageLink(10, 0, searchText, {
  153 + property: 'name',
  154 + direction: Direction.ASC
  155 + });
  156 + return this.tenantProfileService.getTenantProfileInfos(pageLink, {ignoreLoading: true}).pipe(
  157 + map(pageData => {
  158 + return pageData.data;
  159 + })
  160 + );
  161 + }
  162 +
  163 + clear() {
  164 + this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(null, {emitEvent: true});
  165 + setTimeout(() => {
  166 + this.tenantProfileInput.nativeElement.blur();
  167 + this.tenantProfileInput.nativeElement.focus();
  168 + }, 0);
  169 + }
  170 +
  171 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone">
  19 + <button mat-raised-button color="primary"
  20 + [disabled]="(isLoading$ | async)"
  21 + (click)="onEntityAction($event, 'setDefault')"
  22 + [fxShow]="!isEdit && !entity?.default">
  23 + {{'tenant-profile.set-default' | translate }}
  24 + </button>
  25 + <button mat-raised-button color="primary"
  26 + [disabled]="(isLoading$ | async)"
  27 + (click)="onEntityAction($event, 'delete')"
  28 + [fxShow]="!hideDelete() && !isEdit">
  29 + {{'tenant-profile.delete' | translate }}
  30 + </button>
  31 + <div fxLayout="row" fxLayout.xs="column">
  32 + <button mat-raised-button
  33 + ngxClipboard
  34 + (cbOnSuccess)="onTenantProfileIdCopied($event)"
  35 + [cbContent]="entity?.id?.id"
  36 + [fxShow]="!isEdit">
  37 + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
  38 + <span translate>tenant-profile.copyId</span>
  39 + </button>
  40 + </div>
  41 +</div>
  42 +<div class="mat-padding" fxLayout="column">
  43 + <form [formGroup]="entityForm">
  44 + <fieldset [disabled]="(isLoading$ | async) || !isEdit">
  45 + <mat-form-field class="mat-block">
  46 + <mat-label translate>tenant-profile.name</mat-label>
  47 + <input matInput formControlName="name" required/>
  48 + <mat-error *ngIf="entityForm.get('name').hasError('required')">
  49 + {{ 'tenant-profile.name-required' | translate }}
  50 + </mat-error>
  51 + </mat-form-field>
  52 + <div fxLayout="column">
  53 + <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
  54 + <div>{{ 'tenant.isolated-tb-core' | translate }}</div>
  55 + <div class="tb-hint">{{'tenant.isolated-tb-core-details' | translate}}</div>
  56 + </mat-checkbox>
  57 + <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbRuleEngine">
  58 + <div>{{ 'tenant.isolated-tb-rule-engine' | translate }}</div>
  59 + <div class="tb-hint">{{'tenant.isolated-tb-rule-engine-details' | translate}}</div>
  60 + </mat-checkbox>
  61 + </div>
  62 + <mat-form-field class="mat-block">
  63 + <mat-label translate>tenant-profile.description</mat-label>
  64 + <textarea matInput formControlName="description" rows="2"></textarea>
  65 + </mat-form-field>
  66 + </fieldset>
  67 + </form>
  68 +</div>
ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss renamed from ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, Input } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { FormBuilder, FormGroup, Validators } from '@angular/forms';
  21 +import { TenantProfile } from '@app/shared/models/tenant.model';
  22 +import { ActionNotificationShow } from '@app/core/notification/notification.actions';
  23 +import { TranslateService } from '@ngx-translate/core';
  24 +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
  25 +import { EntityComponent } from '../entity/entity.component';
  26 +
  27 +@Component({
  28 + selector: 'tb-tenant-profile',
  29 + templateUrl: './tenant-profile.component.html',
  30 + styleUrls: ['./tenant-profile.component.scss']
  31 +})
  32 +export class TenantProfileComponent extends EntityComponent<TenantProfile> {
  33 +
  34 + @Input()
  35 + standalone = false;
  36 +
  37 + constructor(protected store: Store<AppState>,
  38 + protected translate: TranslateService,
  39 + @Inject('entity') protected entityValue: TenantProfile,
  40 + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<TenantProfile>,
  41 + protected fb: FormBuilder) {
  42 + super(store, fb, entityValue, entitiesTableConfigValue);
  43 + }
  44 +
  45 + hideDelete() {
  46 + if (this.entitiesTableConfig) {
  47 + return !this.entitiesTableConfig.deleteEnabled(this.entity);
  48 + } else {
  49 + return false;
  50 + }
  51 + }
  52 +
  53 + buildForm(entity: TenantProfile): FormGroup {
  54 + return this.fb.group(
  55 + {
  56 + name: [entity ? entity.name : '', [Validators.required]],
  57 + isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
  58 + isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
  59 + description: [entity ? entity.description : '', []],
  60 + }
  61 + );
  62 + }
  63 +
  64 + updateForm(entity: TenantProfile) {
  65 + this.entityForm.patchValue({name: entity.name});
  66 + this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});
  67 + this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});
  68 + this.entityForm.patchValue({description: entity.description});
  69 + }
  70 +
  71 + updateFormState() {
  72 + if (this.entityForm) {
  73 + if (this.isEditValue) {
  74 + this.entityForm.enable({emitEvent: false});
  75 + if (!this.isAdd) {
  76 + this.entityForm.get('isolatedTbCore').disable({emitEvent: false});
  77 + this.entityForm.get('isolatedTbRuleEngine').disable({emitEvent: false});
  78 + }
  79 + } else {
  80 + this.entityForm.disable({emitEvent: false});
  81 + }
  82 + }
  83 + }
  84 +
  85 + onTenantProfileIdCopied(event) {
  86 + this.store.dispatch(new ActionNotificationShow(
  87 + {
  88 + message: this.translate.instant('tenant-profile.idCopiedMessage'),
  89 + type: 'success',
  90 + duration: 750,
  91 + verticalPosition: 'bottom',
  92 + horizontalPosition: 'right'
  93 + }));
  94 + }
  95 +
  96 +}
@@ -29,6 +29,7 @@ import { EntityViewModule } from '@modules/home/pages/entity-view/entity-view.mo @@ -29,6 +29,7 @@ import { EntityViewModule } from '@modules/home/pages/entity-view/entity-view.mo
29 import { RuleChainModule } from '@modules/home/pages/rulechain/rulechain.module'; 29 import { RuleChainModule } from '@modules/home/pages/rulechain/rulechain.module';
30 import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module'; 30 import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module';
31 import { DashboardModule } from '@modules/home/pages/dashboard/dashboard.module'; 31 import { DashboardModule } from '@modules/home/pages/dashboard/dashboard.module';
  32 +import { TenantProfileModule } from './tenant-profile/tenant-profile.module';
32 import { MODULES_MAP } from '@shared/public-api'; 33 import { MODULES_MAP } from '@shared/public-api';
33 import { modulesMap } from '../../common/modules-map'; 34 import { modulesMap } from '../../common/modules-map';
34 35
@@ -37,6 +38,7 @@ import { modulesMap } from '../../common/modules-map'; @@ -37,6 +38,7 @@ import { modulesMap } from '../../common/modules-map';
37 AdminModule, 38 AdminModule,
38 HomeLinksModule, 39 HomeLinksModule,
39 ProfileModule, 40 ProfileModule,
  41 + TenantProfileModule,
40 TenantModule, 42 TenantModule,
41 DeviceModule, 43 DeviceModule,
42 AssetModule, 44 AssetModule,
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { NgModule } from '@angular/core';
  18 +import { RouterModule, Routes } from '@angular/router';
  19 +
  20 +import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
  21 +import { Authority } from '@shared/models/authority.enum';
  22 +import { TenantProfilesTableConfigResolver } from './tenant-profiles-table-config.resolver';
  23 +
  24 +const routes: Routes = [
  25 + {
  26 + path: 'tenantProfiles',
  27 + data: {
  28 + breadcrumb: {
  29 + label: 'tenant-profile.tenant-profiles',
  30 + icon: 'mdi:alpha-t-box'
  31 + }
  32 + },
  33 + children: [
  34 + {
  35 + path: '',
  36 + component: EntitiesTableComponent,
  37 + data: {
  38 + auth: [Authority.SYS_ADMIN],
  39 + title: 'tenant-profile.tenant-profiles'
  40 + },
  41 + resolve: {
  42 + entitiesTableConfig: TenantProfilesTableConfigResolver
  43 + }
  44 + }
  45 + ]
  46 + }
  47 +];
  48 +
  49 +@NgModule({
  50 + imports: [RouterModule.forChild(routes)],
  51 + exports: [RouterModule],
  52 + providers: [
  53 + TenantProfilesTableConfigResolver
  54 + ]
  55 +})
  56 +export class TenantProfileRoutingModule { }
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<mat-tab *ngIf="entity"
  19 + label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
  20 + <tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"
  21 + [active]="attributesTab.isActive"
  22 + [entityId]="entity.id"
  23 + [entityName]="entity.name">
  24 + </tb-attribute-table>
  25 +</mat-tab>
  26 +<mat-tab *ngIf="entity"
  27 + label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
  28 + <tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
  29 + disableAttributeScopeSelection
  30 + [active]="telemetryTab.isActive"
  31 + [entityId]="entity.id"
  32 + [entityName]="entity.name">
  33 + </tb-attribute-table>
  34 +</mat-tab>
  35 +<mat-tab *ngIf="entity"
  36 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  37 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  38 +</mat-tab>
  39 +<mat-tab *ngIf="entity"
  40 + label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
  41 + <tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="nullUid"
  42 + [entityId]="entity.id"></tb-event-table>
  43 +</mat-tab>
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
  21 +import { TenantProfile } from '@shared/models/tenant.model';
  22 +
  23 +@Component({
  24 + selector: 'tb-tenant-profile-tabs',
  25 + templateUrl: './tenant-profile-tabs.component.html',
  26 + styleUrls: []
  27 +})
  28 +export class TenantProfileTabsComponent extends EntityTabsComponent<TenantProfile> {
  29 +
  30 + constructor(protected store: Store<AppState>) {
  31 + super(store);
  32 + }
  33 +
  34 + ngOnInit() {
  35 + super.ngOnInit();
  36 + }
  37 +
  38 +}
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { NgModule } from '@angular/core';
  18 +import { CommonModule } from '@angular/common';
  19 +import { SharedModule } from '@shared/shared.module';
  20 +import { HomeComponentsModule } from '@modules/home/components/home-components.module';
  21 +import { TenantProfileRoutingModule } from './tenant-profile-routing.module';
  22 +import { TenantProfileTabsComponent } from './tenant-profile-tabs.component';
  23 +
  24 +@NgModule({
  25 + declarations: [
  26 + TenantProfileTabsComponent
  27 + ],
  28 + imports: [
  29 + CommonModule,
  30 + SharedModule,
  31 + HomeComponentsModule,
  32 + TenantProfileRoutingModule
  33 + ]
  34 +})
  35 +export class TenantProfileModule { }
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { Resolve } from '@angular/router';
  19 +import { TenantProfile } from '@shared/models/tenant.model';
  20 +import {
  21 + checkBoxCell,
  22 + DateEntityTableColumn,
  23 + EntityTableColumn,
  24 + EntityTableConfig
  25 +} from '@home/models/entity/entities-table-config.models';
  26 +import { TranslateService } from '@ngx-translate/core';
  27 +import { DatePipe } from '@angular/common';
  28 +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
  29 +import { EntityAction } from '@home/models/entity/entity-component.models';
  30 +import { TenantProfileService } from '@core/http/tenant-profile.service';
  31 +import { TenantProfileComponent } from '../../components/profile/tenant-profile.component';
  32 +import { TenantProfileTabsComponent } from './tenant-profile-tabs.component';
  33 +import { DialogService } from '@core/services/dialog.service';
  34 +
  35 +@Injectable()
  36 +export class TenantProfilesTableConfigResolver implements Resolve<EntityTableConfig<TenantProfile>> {
  37 +
  38 + private readonly config: EntityTableConfig<TenantProfile> = new EntityTableConfig<TenantProfile>();
  39 +
  40 + constructor(private tenantProfileService: TenantProfileService,
  41 + private translate: TranslateService,
  42 + private datePipe: DatePipe,
  43 + private dialogService: DialogService) {
  44 +
  45 + this.config.entityType = EntityType.TENANT_PROFILE;
  46 + this.config.entityComponent = TenantProfileComponent;
  47 + this.config.entityTabsComponent = TenantProfileTabsComponent;
  48 + this.config.entityTranslations = entityTypeTranslations.get(EntityType.TENANT_PROFILE);
  49 + this.config.entityResources = entityTypeResources.get(EntityType.TENANT_PROFILE);
  50 +
  51 + this.config.columns.push(
  52 + new DateEntityTableColumn<TenantProfile>('createdTime', 'common.created-time', this.datePipe, '150px'),
  53 + new EntityTableColumn<TenantProfile>('name', 'tenant-profile.name', '40%'),
  54 + new EntityTableColumn<TenantProfile>('description', 'tenant-profile.description', '60%'),
  55 + new EntityTableColumn<TenantProfile>('isDefault', 'tenant-profile.default', '60px',
  56 + entity => {
  57 + return checkBoxCell(entity.default);
  58 + })
  59 + );
  60 +
  61 + this.config.cellActionDescriptors.push(
  62 + {
  63 + name: this.translate.instant('tenant-profile.set-default'),
  64 + icon: 'flag',
  65 + isEnabled: (tenantProfile) => !tenantProfile.default,
  66 + onAction: ($event, entity) => this.setDefaultTenantProfile($event, entity)
  67 + }
  68 + );
  69 +
  70 + this.config.deleteEntityTitle = tenantProfile => this.translate.instant('tenant-profile.delete-tenant-profile-title',
  71 + { tenantProfileName: tenantProfile.name });
  72 + this.config.deleteEntityContent = () => this.translate.instant('tenant-profile.delete-tenant-profile-text');
  73 + this.config.deleteEntitiesTitle = count => this.translate.instant('tenant-profile.delete-tenant-profiles-title', {count});
  74 + this.config.deleteEntitiesContent = () => this.translate.instant('tenant-profile.delete-tenant-profiles-text');
  75 +
  76 + this.config.entitiesFetchFunction = pageLink => this.tenantProfileService.getTenantProfiles(pageLink);
  77 + this.config.loadEntity = id => this.tenantProfileService.getTenantProfile(id.id);
  78 + this.config.saveEntity = tenantProfile => this.tenantProfileService.saveTenantProfile(tenantProfile);
  79 + this.config.deleteEntity = id => this.tenantProfileService.deleteTenantProfile(id.id);
  80 + this.config.onEntityAction = action => this.onTenantProfileAction(action);
  81 + this.config.deleteEnabled = (tenantProfile) => tenantProfile && !tenantProfile.default;
  82 + this.config.entitySelectionEnabled = (tenantProfile) => tenantProfile && !tenantProfile.default;
  83 + }
  84 +
  85 + resolve(): EntityTableConfig<TenantProfile> {
  86 + this.config.tableTitle = this.translate.instant('tenant-profile.tenant-profiles');
  87 +
  88 + return this.config;
  89 + }
  90 +
  91 + setDefaultTenantProfile($event: Event, tenantProfile: TenantProfile) {
  92 + if ($event) {
  93 + $event.stopPropagation();
  94 + }
  95 + this.dialogService.confirm(
  96 + this.translate.instant('tenant-profile.set-default-tenant-profile-title', {tenantProfileName: tenantProfile.name}),
  97 + this.translate.instant('tenant-profile.set-default-tenant-profile-text'),
  98 + this.translate.instant('action.no'),
  99 + this.translate.instant('action.yes'),
  100 + true
  101 + ).subscribe((res) => {
  102 + if (res) {
  103 + this.tenantProfileService.setDefaultTenantProfile(tenantProfile.id.id).subscribe(
  104 + () => {
  105 + this.config.table.updateData();
  106 + }
  107 + );
  108 + }
  109 + }
  110 + );
  111 + }
  112 +
  113 + onTenantProfileAction(action: EntityAction<TenantProfile>): boolean {
  114 + switch (action.action) {
  115 + case 'setDefault':
  116 + this.setDefaultTenantProfile(action.event, action.entity);
  117 + return true;
  118 + }
  119 + return false;
  120 + }
  121 +
  122 +}
@@ -49,6 +49,11 @@ @@ -49,6 +49,11 @@
49 {{ 'tenant.title-required' | translate }} 49 {{ 'tenant.title-required' | translate }}
50 </mat-error> 50 </mat-error>
51 </mat-form-field> 51 </mat-form-field>
  52 + <tb-tenant-profile-autocomplete
  53 + [selectDefaultProfile]="isAdd"
  54 + required
  55 + formControlName="tenantProfileId">
  56 + </tb-tenant-profile-autocomplete>
52 <div formGroupName="additionalInfo" fxLayout="column"> 57 <div formGroupName="additionalInfo" fxLayout="column">
53 <mat-form-field class="mat-block"> 58 <mat-form-field class="mat-block">
54 <mat-label translate>tenant.description</mat-label> 59 <mat-label translate>tenant.description</mat-label>
@@ -56,16 +61,6 @@ @@ -56,16 +61,6 @@
56 </mat-form-field> 61 </mat-form-field>
57 </div> 62 </div>
58 <tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact> 63 <tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact>
59 - <!--div fxLayout="column">  
60 - <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">  
61 - <div>{{ 'tenant.isolated-tb-core' | translate }}</div>  
62 - <div class="tb-hint">{{'tenant.isolated-tb-core-details' | translate}}</div>  
63 - </mat-checkbox>  
64 - <mat-checkbox class="hinted-checkbox" formControlName="isolatedTbRuleEngine">  
65 - <div>{{ 'tenant.isolated-tb-rule-engine' | translate }}</div>  
66 - <div class="tb-hint">{{'tenant.isolated-tb-rule-engine-details' | translate}}</div>  
67 - </mat-checkbox>  
68 - </div-->  
69 </fieldset> 64 </fieldset>
70 </form> 65 </form>
71 </div> 66 </div>
@@ -27,7 +27,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod @@ -27,7 +27,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
27 @Component({ 27 @Component({
28 selector: 'tb-tenant', 28 selector: 'tb-tenant',
29 templateUrl: './tenant.component.html', 29 templateUrl: './tenant.component.html',
30 - styleUrls: ['./tenant.component.scss'] 30 + styleUrls: []
31 }) 31 })
32 export class TenantComponent extends ContactBasedComponent<TenantInfo> { 32 export class TenantComponent extends ContactBasedComponent<TenantInfo> {
33 33
@@ -52,8 +52,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> { @@ -52,8 +52,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
52 { 52 {
53 title: [entity ? entity.title : '', [Validators.required]], 53 title: [entity ? entity.title : '', [Validators.required]],
54 tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]], 54 tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]],
55 - // isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],  
56 - // isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],  
57 additionalInfo: this.fb.group( 55 additionalInfo: this.fb.group(
58 { 56 {
59 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''] 57 description: [entity && entity.additionalInfo ? entity.additionalInfo.description : '']
@@ -66,8 +64,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> { @@ -66,8 +64,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
66 updateEntityForm(entity: Tenant) { 64 updateEntityForm(entity: Tenant) {
67 this.entityForm.patchValue({title: entity.title}); 65 this.entityForm.patchValue({title: entity.title});
68 this.entityForm.patchValue({tenantProfileId: entity.tenantProfileId}); 66 this.entityForm.patchValue({tenantProfileId: entity.tenantProfileId});
69 - // this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});  
70 - // this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});  
71 this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); 67 this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}});
72 } 68 }
73 69
@@ -75,10 +71,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> { @@ -75,10 +71,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
75 if (this.entityForm) { 71 if (this.entityForm) {
76 if (this.isEditValue) { 72 if (this.isEditValue) {
77 this.entityForm.enable({emitEvent: false}); 73 this.entityForm.enable({emitEvent: false});
78 - /* if (!this.isAdd) {  
79 - this.entityForm.get('isolatedTbCore').disable({emitEvent: false});  
80 - this.entityForm.get('isolatedTbRuleEngine').disable({emitEvent: false});  
81 - } */  
82 } else { 74 } else {
83 this.entityForm.disable({emitEvent: false}); 75 this.entityForm.disable({emitEvent: false});
84 } 76 }
@@ -16,7 +16,16 @@ @@ -16,7 +16,16 @@
16 16
17 import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; 17 import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
18 import { HasUUID } from '@shared/models/id/has-uuid'; 18 import { HasUUID } from '@shared/models/id/has-uuid';
  19 +import { isDefinedAndNotNull } from '@core/utils';
19 20
20 export interface EntityId extends HasUUID { 21 export interface EntityId extends HasUUID {
21 entityType: EntityType | AliasEntityType; 22 entityType: EntityType | AliasEntityType;
22 } 23 }
  24 +
  25 +export function entityIdEquals(entityId1: EntityId, entityId2: EntityId): boolean {
  26 + if (isDefinedAndNotNull(entityId1) && isDefinedAndNotNull(entityId2)) {
  27 + return entityId1.id === entityId2.id && entityId1.entityType === entityId2.entityType;
  28 + } else {
  29 + return entityId1 === entityId2;
  30 + }
  31 +}
@@ -26,7 +26,7 @@ export interface TenantProfileData { @@ -26,7 +26,7 @@ export interface TenantProfileData {
26 export interface TenantProfile extends BaseData<TenantProfileId> { 26 export interface TenantProfile extends BaseData<TenantProfileId> {
27 name: string; 27 name: string;
28 description: string; 28 description: string;
29 - isDefault: boolean; 29 + default: boolean;
30 isolatedTbCore: boolean; 30 isolatedTbCore: boolean;
31 isolatedTbRuleEngine: boolean; 31 isolatedTbRuleEngine: boolean;
32 profileData: TenantProfileData; 32 profileData: TenantProfileData;
@@ -1683,7 +1683,23 @@ @@ -1683,7 +1683,23 @@
1683 "tenant-profile-details": "Tenant profile details", 1683 "tenant-profile-details": "Tenant profile details",
1684 "no-tenant-profiles-text": "No tenant profiles found", 1684 "no-tenant-profiles-text": "No tenant profiles found",
1685 "search": "Search tenant profiles", 1685 "search": "Search tenant profiles",
1686 - "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected" 1686 + "selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected",
  1687 + "no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.",
  1688 + "tenant-profile-required": "Tenant profile is required",
  1689 + "idCopiedMessage": "Tenant profile Id has been copied to clipboard",
  1690 + "set-default": "Make tenant profile default",
  1691 + "delete": "Delete tenant profile",
  1692 + "copyId": "Copy tenant profile Id",
  1693 + "name": "Name",
  1694 + "name-required": "Name is required.",
  1695 + "description": "Description",
  1696 + "default": "Default",
  1697 + "delete-tenant-profile-title": "Are you sure you want to delete the tenant profile '{{tenantProfileName}}'?",
  1698 + "delete-tenant-profile-text": "Be careful, after the confirmation the tenant profile and all related data will become unrecoverable.",
  1699 + "delete-tenant-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 tenant profile} other {# tenant profiles} }?",
  1700 + "delete-tenant-profiles-text": "Be careful, after the confirmation all selected tenant profiles will be removed and all related data will become unrecoverable.",
  1701 + "set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' root?",
  1702 + "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified."
1687 }, 1703 },
1688 "timeinterval": { 1704 "timeinterval": {
1689 "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", 1705 "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",