Browse Source

UI: Tenant Profiles

pull/3477/head
Igor Kulikov 6 years ago
parent
commit
f39d663827
  1. 67
      ui-ngx/src/app/core/http/tenant-profile.service.ts
  2. 2
      ui-ngx/src/app/core/interceptors/global-http-interceptor.ts
  3. 15
      ui-ngx/src/app/core/services/menu.service.ts
  4. 16
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  5. 10
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  6. 46
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html
  7. 171
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts
  8. 68
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html
  9. 0
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss
  10. 96
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts
  11. 2
      ui-ngx/src/app/modules/home/pages/home-pages.module.ts
  12. 56
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts
  13. 43
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.html
  14. 38
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.ts
  15. 35
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile.module.ts
  16. 122
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts
  17. 15
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html
  18. 10
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts
  19. 9
      ui-ngx/src/app/shared/models/id/entity-id.ts
  20. 2
      ui-ngx/src/app/shared/models/tenant.model.ts
  21. 18
      ui-ngx/src/assets/locale/locale.constant-en_US.json

67
ui-ngx/src/app/core/http/tenant-profile.service.ts

@ -0,0 +1,67 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils';
import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { TenantProfile } from '@shared/models/tenant.model';
import { EntityInfoData } from '@shared/models/entity.models';
@Injectable({
providedIn: 'root'
})
export class TenantProfileService {
constructor(
private http: HttpClient
) { }
public getTenantProfiles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<TenantProfile>> {
return this.http.get<PageData<TenantProfile>>(`/api/tenantProfiles${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
}
public getTenantProfile(tenantProfileId: string, config?: RequestConfig): Observable<TenantProfile> {
return this.http.get<TenantProfile>(`/api/tenantProfile/${tenantProfileId}`, defaultHttpOptionsFromConfig(config));
}
public saveTenantProfile(tenantProfile: TenantProfile, config?: RequestConfig): Observable<TenantProfile> {
return this.http.post<TenantProfile>('/api/tenantProfile', tenantProfile, defaultHttpOptionsFromConfig(config));
}
public deleteTenantProfile(tenantProfileId: string, config?: RequestConfig) {
return this.http.delete(`/api/tenantProfile/${tenantProfileId}`, defaultHttpOptionsFromConfig(config));
}
public setDefaultTenantProfile(tenantProfileId: string, config?: RequestConfig): Observable<TenantProfile> {
return this.http.post<TenantProfile>(`/api/tenantProfile/${tenantProfileId}/default`, defaultHttpOptionsFromConfig(config));
}
public getDefaultTenantProfileInfo(config?: RequestConfig): Observable<EntityInfoData> {
return this.http.get<EntityInfoData>('/api/tenantProfileInfo/default', defaultHttpOptionsFromConfig(config));
}
public getTenantProfileInfo(tenantProfileId: string, config?: RequestConfig): Observable<EntityInfoData> {
return this.http.get<EntityInfoData>(`/api/tenantProfileInfo/${tenantProfileId}`, defaultHttpOptionsFromConfig(config));
}
public getTenantProfileInfos(pageLink: PageLink, config?: RequestConfig): Observable<PageData<EntityInfoData>> {
return this.http.get<PageData<EntityInfoData>>(`/api/tenantProfileInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
}
}

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

@ -249,7 +249,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor {
} else {
this.activeRequests--;
}
if (this.activeRequests === 1) {
if (this.activeRequests === 1 && isLoading) {
this.store.dispatch(new ActionLoadStart());
} else if (this.activeRequests === 0) {
this.store.dispatch(new ActionLoadFinish());

15
ui-ngx/src/app/core/services/menu.service.ts

@ -85,6 +85,13 @@ export class MenuService {
path: '/tenants',
icon: 'supervisor_account'
},
{
name: 'tenant-profile.tenant-profiles',
type: 'link',
path: '/tenantProfiles',
icon: 'mdi:alpha-t-box',
isMdiIcon: true
},
{
name: 'widget.widget-library',
type: 'link',
@ -132,7 +139,13 @@ export class MenuService {
name: 'tenant.tenants',
icon: 'supervisor_account',
path: '/tenants'
}
},
{
name: 'tenant-profile.tenant-profiles',
icon: 'mdi:alpha-t-box',
isMdiIcon: true,
path: '/tenantProfiles'
},
]
},
{

16
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -36,9 +36,9 @@ import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { EntitiesDataSource } from '@home/models/datasource/entity-datasource';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { catchError, debounceTime, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { Direction, SortOrder } from '@shared/models/page/sort-order';
import { forkJoin, fromEvent, merge, Observable, Subscription } from 'rxjs';
import { forkJoin, fromEvent, merge, Observable, of, Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import { BaseData, HasId } from '@shared/models/base-data';
import { ActivatedRoute } from '@angular/router';
@ -59,6 +59,7 @@ import { DAY, historyInterval, HistoryWindowType, Timewindow } from '@shared/mod
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { isDefined, isUndefined } from '@core/utils';
import { HasUUID } from '../../../../shared/models/id/has-uuid';
@Component({
selector: 'tb-entities-table',
@ -401,16 +402,19 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn
true
).subscribe((result) => {
if (result) {
const tasks: Observable<any>[] = [];
const tasks: Observable<HasUUID>[] = [];
entities.forEach((entity) => {
if (this.entitiesTableConfig.deleteEnabled(entity)) {
tasks.push(this.entitiesTableConfig.deleteEntity(entity.id));
tasks.push(this.entitiesTableConfig.deleteEntity(entity.id).pipe(
map(() => entity.id),
catchError(() => of(null)
)));
}
});
forkJoin(tasks).subscribe(
() => {
(ids) => {
this.updateData();
this.entitiesTableConfig.entitiesDeleted(entities.map((e) => e.id));
this.entitiesTableConfig.entitiesDeleted(ids.filter(id => id !== null));
}
);
}

10
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -84,6 +84,8 @@ import { UserFilterDialogComponent } from '@home/components/filter/user-filter-d
import { FilterUserInfoComponent } from './filter/filter-user-info.component';
import { FilterUserInfoDialogComponent } from './filter/filter-user-info-dialog.component';
import { FilterPredicateValueComponent } from './filter/filter-predicate-value.component';
import { TenantProfileAutocompleteComponent } from './profile/tenant-profile-autocomplete.component';
import { TenantProfileComponent } from './profile/tenant-profile.component';
@NgModule({
declarations:
@ -150,7 +152,9 @@ import { FilterPredicateValueComponent } from './filter/filter-predicate-value.c
UserFilterDialogComponent,
FilterUserInfoComponent,
FilterUserInfoDialogComponent,
FilterPredicateValueComponent
FilterPredicateValueComponent,
TenantProfileAutocompleteComponent,
TenantProfileComponent
],
imports: [
CommonModule,
@ -206,7 +210,9 @@ import { FilterPredicateValueComponent } from './filter/filter-predicate-value.c
FiltersDialogComponent,
FilterSelectComponent,
FiltersEditComponent,
UserFilterDialogComponent
UserFilterDialogComponent,
TenantProfileAutocompleteComponent,
TenantProfileComponent
],
providers: [
WidgetComponentService,

46
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html

@ -0,0 +1,46 @@
<!--
Copyright © 2016-2020 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.
-->
<mat-form-field [formGroup]="selectTenantProfileFormGroup" class="mat-block">
<input matInput type="text" placeholder="{{ 'tenant-profile.tenant-profile' | translate }}"
#tenantProfileInput
formControlName="tenantProfile"
[required]="required"
[matAutocomplete]="tenantProfileAutocomplete">
<button *ngIf="selectTenantProfileFormGroup.get('tenantProfile').value && !disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete
class="tb-autocomplete"
#tenantProfileAutocomplete="matAutocomplete"
[displayWith]="displayTenantProfileFn">
<mat-option *ngFor="let tenantProfile of filteredTenantProfiles | async" [value]="tenantProfile">
<span [innerHTML]="tenantProfile.name | highlight:searchText"></span>
</mat-option>
<mat-option *ngIf="!(filteredTenantProfiles | async)?.length" [value]="null">
<span>
{{ translate.get('tenant-profile.no-tenant-profiles-matching', {entity: searchText}) | async }}
</span>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="selectTenantProfileFormGroup.get('tenantProfile').hasError('required')">
{{ 'tenant-profile.tenant-profile-required' | translate }}
</mat-error>
</mat-form-field>

171
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts

@ -0,0 +1,171 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Observable } from 'rxjs';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { map, mergeMap, startWith, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { TenantProfileId } from '@shared/models/id/tenant-profile-id';
import { EntityInfoData } from '@shared/models/entity.models';
import { TenantProfileService } from '@core/http/tenant-profile.service';
import { entityIdEquals } from '../../../../shared/models/id/entity-id';
@Component({
selector: 'tb-tenant-profile-autocomplete',
templateUrl: './tenant-profile-autocomplete.component.html',
styleUrls: [],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TenantProfileAutocompleteComponent),
multi: true
}]
})
export class TenantProfileAutocompleteComponent implements ControlValueAccessor, OnInit {
selectTenantProfileFormGroup: FormGroup;
modelValue: TenantProfileId | null;
@Input()
selectDefaultProfile = false;
private requiredValue: boolean;
get required(): boolean {
return this.requiredValue;
}
@Input()
set required(value: boolean) {
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
disabled: boolean;
@ViewChild('tenantProfileInput', {static: true}) tenantProfileInput: ElementRef;
filteredTenantProfiles: Observable<Array<EntityInfoData>>;
searchText = '';
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
private tenantProfileService: TenantProfileService,
private fb: FormBuilder) {
this.selectTenantProfileFormGroup = this.fb.group({
tenantProfile: [null]
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
ngOnInit() {
this.filteredTenantProfiles = this.selectTenantProfileFormGroup.get('tenantProfile').valueChanges
.pipe(
tap((value: EntityInfoData | string) => {
let modelValue: TenantProfileId | null;
if (typeof value === 'string' || !value) {
modelValue = null;
} else {
modelValue = new TenantProfileId(value.id.id);
}
this.updateView(modelValue);
}),
startWith<string | EntityInfoData>(''),
map(value => value ? (typeof value === 'string' ? value : value.name) : ''),
mergeMap(name => this.fetchTenantProfiles(name) )
);
}
selectDefaultTenantProfileIfNeeded(): void {
if (this.selectDefaultProfile && !this.modelValue) {
this.tenantProfileService.getDefaultTenantProfileInfo().subscribe(
(profile) => {
if (profile) {
this.modelValue = new TenantProfileId(profile.id.id);
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: false});
this.propagateChange(this.modelValue);
}
}
);
}
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
writeValue(value: TenantProfileId | null): void {
this.searchText = '';
if (value != null) {
this.tenantProfileService.getTenantProfileInfo(value.id).subscribe(
(profile) => {
this.modelValue = new TenantProfileId(profile.id.id);
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: true});
}
);
} else {
this.modelValue = null;
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(null, {emitEvent: true});
this.selectDefaultTenantProfileIfNeeded();
}
}
updateView(value: TenantProfileId | null) {
if (!entityIdEquals(this.modelValue, value)) {
this.modelValue = value;
this.propagateChange(this.modelValue);
}
}
displayTenantProfileFn(profile?: EntityInfoData): string | undefined {
return profile ? profile.name : undefined;
}
fetchTenantProfiles(searchText?: string): Observable<Array<EntityInfoData>> {
this.searchText = searchText;
const pageLink = new PageLink(10, 0, searchText, {
property: 'name',
direction: Direction.ASC
});
return this.tenantProfileService.getTenantProfileInfos(pageLink, {ignoreLoading: true}).pipe(
map(pageData => {
return pageData.data;
})
);
}
clear() {
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(null, {emitEvent: true});
setTimeout(() => {
this.tenantProfileInput.nativeElement.blur();
this.tenantProfileInput.nativeElement.focus();
}, 0);
}
}

68
ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html

@ -0,0 +1,68 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')"
[fxShow]="!isEdit && !entity?.default">
{{'tenant-profile.set-default' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'delete')"
[fxShow]="!hideDelete() && !isEdit">
{{'tenant-profile.delete' | translate }}
</button>
<div fxLayout="row" fxLayout.xs="column">
<button mat-raised-button
ngxClipboard
(cbOnSuccess)="onTenantProfileIdCopied($event)"
[cbContent]="entity?.id?.id"
[fxShow]="!isEdit">
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon>
<span translate>tenant-profile.copyId</span>
</button>
</div>
</div>
<div class="mat-padding" fxLayout="column">
<form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.name</mat-label>
<input matInput formControlName="name" required/>
<mat-error *ngIf="entityForm.get('name').hasError('required')">
{{ 'tenant-profile.name-required' | translate }}
</mat-error>
</mat-form-field>
<div fxLayout="column">
<mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
<div>{{ 'tenant.isolated-tb-core' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-core-details' | translate}}</div>
</mat-checkbox>
<mat-checkbox class="hinted-checkbox" formControlName="isolatedTbRuleEngine">
<div>{{ 'tenant.isolated-tb-rule-engine' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-rule-engine-details' | translate}}</div>
</mat-checkbox>
</div>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea>
</mat-form-field>
</fieldset>
</form>
</div>

0
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.scss → ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.scss

96
ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts

@ -0,0 +1,96 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, Inject, Input } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { TenantProfile } from '@app/shared/models/tenant.model';
import { ActionNotificationShow } from '@app/core/notification/notification.actions';
import { TranslateService } from '@ngx-translate/core';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { EntityComponent } from '../entity/entity.component';
@Component({
selector: 'tb-tenant-profile',
templateUrl: './tenant-profile.component.html',
styleUrls: ['./tenant-profile.component.scss']
})
export class TenantProfileComponent extends EntityComponent<TenantProfile> {
@Input()
standalone = false;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Inject('entity') protected entityValue: TenantProfile,
@Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<TenantProfile>,
protected fb: FormBuilder) {
super(store, fb, entityValue, entitiesTableConfigValue);
}
hideDelete() {
if (this.entitiesTableConfig) {
return !this.entitiesTableConfig.deleteEnabled(this.entity);
} else {
return false;
}
}
buildForm(entity: TenantProfile): FormGroup {
return this.fb.group(
{
name: [entity ? entity.name : '', [Validators.required]],
isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
description: [entity ? entity.description : '', []],
}
);
}
updateForm(entity: TenantProfile) {
this.entityForm.patchValue({name: entity.name});
this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});
this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});
this.entityForm.patchValue({description: entity.description});
}
updateFormState() {
if (this.entityForm) {
if (this.isEditValue) {
this.entityForm.enable({emitEvent: false});
if (!this.isAdd) {
this.entityForm.get('isolatedTbCore').disable({emitEvent: false});
this.entityForm.get('isolatedTbRuleEngine').disable({emitEvent: false});
}
} else {
this.entityForm.disable({emitEvent: false});
}
}
}
onTenantProfileIdCopied(event) {
this.store.dispatch(new ActionNotificationShow(
{
message: this.translate.instant('tenant-profile.idCopiedMessage'),
type: 'success',
duration: 750,
verticalPosition: 'bottom',
horizontalPosition: 'right'
}));
}
}

2
ui-ngx/src/app/modules/home/pages/home-pages.module.ts

@ -29,6 +29,7 @@ import { EntityViewModule } from '@modules/home/pages/entity-view/entity-view.mo
import { RuleChainModule } from '@modules/home/pages/rulechain/rulechain.module';
import { WidgetLibraryModule } from '@modules/home/pages/widget/widget-library.module';
import { DashboardModule } from '@modules/home/pages/dashboard/dashboard.module';
import { TenantProfileModule } from './tenant-profile/tenant-profile.module';
import { MODULES_MAP } from '@shared/public-api';
import { modulesMap } from '../../common/modules-map';
@ -37,6 +38,7 @@ import { modulesMap } from '../../common/modules-map';
AdminModule,
HomeLinksModule,
ProfileModule,
TenantProfileModule,
TenantModule,
DeviceModule,
AssetModule,

56
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts

@ -0,0 +1,56 @@
///
/// Copyright © 2016-2020 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 { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { TenantProfilesTableConfigResolver } from './tenant-profiles-table-config.resolver';
const routes: Routes = [
{
path: 'tenantProfiles',
data: {
breadcrumb: {
label: 'tenant-profile.tenant-profiles',
icon: 'mdi:alpha-t-box'
}
},
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.SYS_ADMIN],
title: 'tenant-profile.tenant-profiles'
},
resolve: {
entitiesTableConfig: TenantProfilesTableConfigResolver
}
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
TenantProfilesTableConfigResolver
]
})
export class TenantProfileRoutingModule { }

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

@ -0,0 +1,43 @@
<!--
Copyright © 2016-2020 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.
-->
<mat-tab *ngIf="entity"
label="{{ 'attribute.attributes' | translate }}" #attributesTab="matTab">
<tb-attribute-table [defaultAttributeScope]="attributeScopes.SERVER_SCOPE"
[active]="attributesTab.isActive"
[entityId]="entity.id"
[entityName]="entity.name">
</tb-attribute-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'attribute.latest-telemetry' | translate }}" #telemetryTab="matTab">
<tb-attribute-table [defaultAttributeScope]="latestTelemetryTypes.LATEST_TELEMETRY"
disableAttributeScopeSelection
[active]="telemetryTab.isActive"
[entityId]="entity.id"
[entityName]="entity.name">
</tb-attribute-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
<tb-event-table [defaultEventType]="eventTypes.ERROR" [active]="eventsTab.isActive" [tenantId]="nullUid"
[entityId]="entity.id"></tb-event-table>
</mat-tab>

38
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-tabs.component.ts

@ -0,0 +1,38 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
import { TenantProfile } from '@shared/models/tenant.model';
@Component({
selector: 'tb-tenant-profile-tabs',
templateUrl: './tenant-profile-tabs.component.html',
styleUrls: []
})
export class TenantProfileTabsComponent extends EntityTabsComponent<TenantProfile> {
constructor(protected store: Store<AppState>) {
super(store);
}
ngOnInit() {
super.ngOnInit();
}
}

35
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile.module.ts

@ -0,0 +1,35 @@
///
/// Copyright © 2016-2020 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 { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '@shared/shared.module';
import { HomeComponentsModule } from '@modules/home/components/home-components.module';
import { TenantProfileRoutingModule } from './tenant-profile-routing.module';
import { TenantProfileTabsComponent } from './tenant-profile-tabs.component';
@NgModule({
declarations: [
TenantProfileTabsComponent
],
imports: [
CommonModule,
SharedModule,
HomeComponentsModule,
TenantProfileRoutingModule
]
})
export class TenantProfileModule { }

122
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts

@ -0,0 +1,122 @@
///
/// Copyright © 2016-2020 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { TenantProfile } from '@shared/models/tenant.model';
import {
checkBoxCell,
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityAction } from '@home/models/entity/entity-component.models';
import { TenantProfileService } from '@core/http/tenant-profile.service';
import { TenantProfileComponent } from '../../components/profile/tenant-profile.component';
import { TenantProfileTabsComponent } from './tenant-profile-tabs.component';
import { DialogService } from '@core/services/dialog.service';
@Injectable()
export class TenantProfilesTableConfigResolver implements Resolve<EntityTableConfig<TenantProfile>> {
private readonly config: EntityTableConfig<TenantProfile> = new EntityTableConfig<TenantProfile>();
constructor(private tenantProfileService: TenantProfileService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialogService: DialogService) {
this.config.entityType = EntityType.TENANT_PROFILE;
this.config.entityComponent = TenantProfileComponent;
this.config.entityTabsComponent = TenantProfileTabsComponent;
this.config.entityTranslations = entityTypeTranslations.get(EntityType.TENANT_PROFILE);
this.config.entityResources = entityTypeResources.get(EntityType.TENANT_PROFILE);
this.config.columns.push(
new DateEntityTableColumn<TenantProfile>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<TenantProfile>('name', 'tenant-profile.name', '40%'),
new EntityTableColumn<TenantProfile>('description', 'tenant-profile.description', '60%'),
new EntityTableColumn<TenantProfile>('isDefault', 'tenant-profile.default', '60px',
entity => {
return checkBoxCell(entity.default);
})
);
this.config.cellActionDescriptors.push(
{
name: this.translate.instant('tenant-profile.set-default'),
icon: 'flag',
isEnabled: (tenantProfile) => !tenantProfile.default,
onAction: ($event, entity) => this.setDefaultTenantProfile($event, entity)
}
);
this.config.deleteEntityTitle = tenantProfile => this.translate.instant('tenant-profile.delete-tenant-profile-title',
{ tenantProfileName: tenantProfile.name });
this.config.deleteEntityContent = () => this.translate.instant('tenant-profile.delete-tenant-profile-text');
this.config.deleteEntitiesTitle = count => this.translate.instant('tenant-profile.delete-tenant-profiles-title', {count});
this.config.deleteEntitiesContent = () => this.translate.instant('tenant-profile.delete-tenant-profiles-text');
this.config.entitiesFetchFunction = pageLink => this.tenantProfileService.getTenantProfiles(pageLink);
this.config.loadEntity = id => this.tenantProfileService.getTenantProfile(id.id);
this.config.saveEntity = tenantProfile => this.tenantProfileService.saveTenantProfile(tenantProfile);
this.config.deleteEntity = id => this.tenantProfileService.deleteTenantProfile(id.id);
this.config.onEntityAction = action => this.onTenantProfileAction(action);
this.config.deleteEnabled = (tenantProfile) => tenantProfile && !tenantProfile.default;
this.config.entitySelectionEnabled = (tenantProfile) => tenantProfile && !tenantProfile.default;
}
resolve(): EntityTableConfig<TenantProfile> {
this.config.tableTitle = this.translate.instant('tenant-profile.tenant-profiles');
return this.config;
}
setDefaultTenantProfile($event: Event, tenantProfile: TenantProfile) {
if ($event) {
$event.stopPropagation();
}
this.dialogService.confirm(
this.translate.instant('tenant-profile.set-default-tenant-profile-title', {tenantProfileName: tenantProfile.name}),
this.translate.instant('tenant-profile.set-default-tenant-profile-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes'),
true
).subscribe((res) => {
if (res) {
this.tenantProfileService.setDefaultTenantProfile(tenantProfile.id.id).subscribe(
() => {
this.config.table.updateData();
}
);
}
}
);
}
onTenantProfileAction(action: EntityAction<TenantProfile>): boolean {
switch (action.action) {
case 'setDefault':
this.setDefaultTenantProfile(action.event, action.entity);
return true;
}
return false;
}
}

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

@ -49,6 +49,11 @@
{{ 'tenant.title-required' | translate }}
</mat-error>
</mat-form-field>
<tb-tenant-profile-autocomplete
[selectDefaultProfile]="isAdd"
required
formControlName="tenantProfileId">
</tb-tenant-profile-autocomplete>
<div formGroupName="additionalInfo" fxLayout="column">
<mat-form-field class="mat-block">
<mat-label translate>tenant.description</mat-label>
@ -56,16 +61,6 @@
</mat-form-field>
</div>
<tb-contact [parentForm]="entityForm" [isEdit]="isEdit"></tb-contact>
<!--div fxLayout="column">
<mat-checkbox class="hinted-checkbox" formControlName="isolatedTbCore">
<div>{{ 'tenant.isolated-tb-core' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-core-details' | translate}}</div>
</mat-checkbox>
<mat-checkbox class="hinted-checkbox" formControlName="isolatedTbRuleEngine">
<div>{{ 'tenant.isolated-tb-rule-engine' | translate }}</div>
<div class="tb-hint">{{'tenant.isolated-tb-rule-engine-details' | translate}}</div>
</mat-checkbox>
</div-->
</fieldset>
</form>
</div>

10
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts

@ -27,7 +27,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
@Component({
selector: 'tb-tenant',
templateUrl: './tenant.component.html',
styleUrls: ['./tenant.component.scss']
styleUrls: []
})
export class TenantComponent extends ContactBasedComponent<TenantInfo> {
@ -52,8 +52,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
{
title: [entity ? entity.title : '', [Validators.required]],
tenantProfileId: [entity ? entity.tenantProfileId : null, [Validators.required]],
// isolatedTbCore: [entity ? entity.isolatedTbCore : false, []],
// isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []],
additionalInfo: this.fb.group(
{
description: [entity && entity.additionalInfo ? entity.additionalInfo.description : '']
@ -66,8 +64,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
updateEntityForm(entity: Tenant) {
this.entityForm.patchValue({title: entity.title});
this.entityForm.patchValue({tenantProfileId: entity.tenantProfileId});
// this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore});
// this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine});
this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}});
}
@ -75,10 +71,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
if (this.entityForm) {
if (this.isEditValue) {
this.entityForm.enable({emitEvent: false});
/* if (!this.isAdd) {
this.entityForm.get('isolatedTbCore').disable({emitEvent: false});
this.entityForm.get('isolatedTbRuleEngine').disable({emitEvent: false});
} */
} else {
this.entityForm.disable({emitEvent: false});
}

9
ui-ngx/src/app/shared/models/id/entity-id.ts

@ -16,7 +16,16 @@
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
import { HasUUID } from '@shared/models/id/has-uuid';
import { isDefinedAndNotNull } from '@core/utils';
export interface EntityId extends HasUUID {
entityType: EntityType | AliasEntityType;
}
export function entityIdEquals(entityId1: EntityId, entityId2: EntityId): boolean {
if (isDefinedAndNotNull(entityId1) && isDefinedAndNotNull(entityId2)) {
return entityId1.id === entityId2.id && entityId1.entityType === entityId2.entityType;
} else {
return entityId1 === entityId2;
}
}

2
ui-ngx/src/app/shared/models/tenant.model.ts

@ -26,7 +26,7 @@ export interface TenantProfileData {
export interface TenantProfile extends BaseData<TenantProfileId> {
name: string;
description: string;
isDefault: boolean;
default: boolean;
isolatedTbCore: boolean;
isolatedTbRuleEngine: boolean;
profileData: TenantProfileData;

18
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1683,7 +1683,23 @@
"tenant-profile-details": "Tenant profile details",
"no-tenant-profiles-text": "No tenant profiles found",
"search": "Search tenant profiles",
"selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected"
"selected-tenant-profiles": "{ count, plural, 1 {1 tenant profile} other {# tenant profiles} } selected",
"no-tenant-profiles-matching": "No tenant profile matching '{{entity}}' were found.",
"tenant-profile-required": "Tenant profile is required",
"idCopiedMessage": "Tenant profile Id has been copied to clipboard",
"set-default": "Make tenant profile default",
"delete": "Delete tenant profile",
"copyId": "Copy tenant profile Id",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
"default": "Default",
"delete-tenant-profile-title": "Are you sure you want to delete the tenant profile '{{tenantProfileName}}'?",
"delete-tenant-profile-text": "Be careful, after the confirmation the tenant profile and all related data will become unrecoverable.",
"delete-tenant-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 tenant profile} other {# tenant profiles} }?",
"delete-tenant-profiles-text": "Be careful, after the confirmation all selected tenant profiles will be removed and all related data will become unrecoverable.",
"set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' root?",
"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."
},
"timeinterval": {
"seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }",

Loading…
Cancel
Save