Browse Source

UI: Added entity details page

pull/5797/head
Vladyslav_Prykhodko 5 years ago
parent
commit
edcde714c7
  1. 14
      ui-ngx/src/app/core/utils.ts
  2. 57
      ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.html
  3. 119
      ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.scss
  4. 154
      ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts
  5. 16
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  6. 2
      ui-ngx/src/app/modules/home/components/entity/entity.component.ts
  7. 3
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  8. 7
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html
  9. 21
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.scss
  10. 9
      ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts
  11. 2
      ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts
  12. 8
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.html
  13. 7
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html
  14. 21
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.scss
  15. 16
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts
  16. 6
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html
  17. 8
      ui-ngx/src/app/modules/home/components/relation/relation-table.component.html
  18. 3
      ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss
  19. 5
      ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts
  20. 38
      ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts
  21. 13
      ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts
  22. 6
      ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html
  23. 5
      ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts
  24. 42
      ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts
  25. 6
      ui-ngx/src/app/modules/home/pages/asset/asset.component.html
  26. 10
      ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts
  27. 167
      ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts
  28. 6
      ui-ngx/src/app/modules/home/pages/customer/customer.component.html
  29. 10
      ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts
  30. 20
      ui-ngx/src/app/modules/home/pages/device-profile/device-profile-routing.module.ts
  31. 13
      ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts
  32. 42
      ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts
  33. 7
      ui-ngx/src/app/modules/home/pages/device/device.component.html
  34. 1
      ui-ngx/src/app/modules/home/pages/device/device.component.ts
  35. 10
      ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts
  36. 131
      ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts
  37. 6
      ui-ngx/src/app/modules/home/pages/edge/edge.component.html
  38. 10
      ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts
  39. 42
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts
  40. 6
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.html
  41. 10
      ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts
  42. 39
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts
  43. 13
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts
  44. 9
      ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html
  45. 20
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profile-routing.module.ts
  46. 15
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts
  47. 55
      ui-ngx/src/app/modules/home/pages/tenant/tenant-routing.module.ts
  48. 7
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.html
  49. 10
      ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts
  50. 43
      ui-ngx/src/app/modules/home/pages/user/user-routing.module.ts
  51. 6
      ui-ngx/src/app/modules/home/pages/user/user.component.html
  52. 27
      ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts
  53. 8
      ui-ngx/src/app/shared/components/breadcrumb.component.ts
  54. 7
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html
  55. 21
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.scss
  56. 10
      ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts
  57. 15
      ui-ngx/src/app/shared/models/constants.ts
  58. 1
      ui-ngx/src/app/shared/models/relation.models.ts
  59. 1
      ui-ngx/src/app/shared/shared.module.ts
  60. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json

14
ui-ngx/src/app/core/utils.ts

@ -20,6 +20,10 @@ import { finalize, share } from 'rxjs/operators';
import { Datasource } from '@app/shared/models/widget.models';
import { EntityId } from '@shared/models/id/entity-id';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { BreadCrumbLabelFunction } from '@shared/components/breadcrumb';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { baseDetailsPageByEntityType } from '@shared/models/constants';
import { EntityType } from '@shared/models/entity-type.models';
const varsRegex = /\${([^}]*)}/g;
@ -460,3 +464,13 @@ export function randomAlphanumeric(length: number): string {
}
return result;
}
export const entityDetailsPageBreadcrumbLabelFunction: BreadCrumbLabelFunction<EntityDetailsPageComponent>
= ((route, translate, component) => {
return component.entity?.name || component.headerSubtitle;
});
export function getEntityDetailsPageURL(id: string, entityType: EntityType): string {
return `${baseDetailsPageByEntityType.get(entityType)}/${id}`;
}

57
ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.html

@ -0,0 +1,57 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<mat-card class="settings-card">
<mat-toolbar class="details-toolbar">
<div class="mat-toolbar-tools" fxLayout="row" fxLayoutAlign="start center">
<div class="tb-details-title-header" fxLayout="column" fxLayoutAlign="center start">
<div class="tb-details-title tb-ellipsis">{{ headerTitle }}</div>
<div class="tb-details-subtitle tb-ellipsis">{{ headerSubtitle }}</div>
</div>
<div class="tb-help" [tb-help]="helpLinkId()"></div>
<span fxFlex></span>
<section *ngIf="!isReadOnly" fxLayout="row" class="tb-header-button" fxLayoutGap="8px">
<button [disabled]="(isLoading$ | async) || detailsForm.invalid || !detailsForm.dirty"
mat-fab
matTooltip="{{ 'action.apply-changes' | translate }}"
matTooltipPosition="above"
color="accent" class="tb-btn-header"
[ngClass]="{'tb-hide': !isEdit}"
(click)="onApplyDetails()">
<mat-icon class="material-icons">done</mat-icon>
</button>
<button [disabled]="(isLoading$ | async)"
mat-fab
matTooltip="{{ 'action.decline-changes' | translate }}"
matTooltipPosition="above"
color="accent" class="tb-btn-header"
(click)="onToggleDetailsEditMode()">
<mat-icon class="material-icons">{{isEdit ? 'close' : 'edit'}}</mat-icon>
</button>
</section>
</div>
</mat-toolbar>
<mat-card-content fxFlex="100">
<mat-tab-group class="tb-absolute-fill" [ngClass]="{'tb-headless': hideDetailsTabs()}" [(selectedIndex)]="selectedTab" fxFill>
<mat-tab label="{{ 'details.details' | translate }}">
<tb-anchor #entityDetailsForm></tb-anchor>
</mat-tab>
<tb-anchor #entityTabs></tb-anchor>
</mat-tab-group>
</mat-card-content>
</mat-card>

119
ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.scss

@ -0,0 +1,119 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import "../../../../../scss/constants";
:host {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.settings-card {
margin: 8px;
padding: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
.details-toolbar {
height: 84px;
min-height: 84px;
border-radius: 4px 4px 0 0;
background: #fff;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
.mat-toolbar-tools {
padding: 0 8px;
}
.tb-details-title-header {
min-width: 0;
width: auto;
}
.tb-details-title {
font-size: 1rem;
font-weight: 500;
@media #{$mat-gt-sm} {
font-size: 1.2rem;
}
}
.tb-details-subtitle {
font-size: 0.9rem;
opacity: .8;
}
.tb-ellipsis {
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@media #{$mat-md} {
width: 80%;
}
@media #{$mat-gt-md} {
width: 60%;
}
.tb-header-button {
.tb-btn-header {
position: relative !important;
display: inline-block !important;
animation: tbMoveFromTopFade .3s ease both;
&.tb-hide {
animation: tbMoveToTopFade .3s ease both;
}
}
}
}
}
:host ::ng-deep {
.tb-help {
.mat-icon-button.mat-primary {
color: rgba(0, 0, 0, 0.52);
}
}
.mat-card-content {
position: relative;
overflow: hidden;
> .mat-tab-group {
> .mat-tab-body-wrapper {
position: absolute;
top: 49px;
left: 0;
right: 0;
bottom: 0;
}
> .mat-tab-header {
.mat-tab-label {
min-width: 40px;
}
}
}
}
}

154
ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts

@ -0,0 +1,154 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ComponentFactoryResolver,
HostBinding,
Injector,
OnDestroy,
OnInit
} from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { BaseData, HasId } from '@shared/models/base-data';
import { ActivatedRoute, Router } from '@angular/router';
import { FormGroup } from '@angular/forms';
import { AssetId } from '@shared/models/id/asset-id';
import { TranslateService } from '@ngx-translate/core';
import { deepClone, mergeDeep } from '@core/utils';
import { BroadcastService } from '@core/services/broadcast.service';
import { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component';
@Component({
selector: 'tb-entity-details-page',
templateUrl: './entity-details-page.component.html',
styleUrls: ['./entity-details-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EntityDetailsPageComponent extends EntityDetailsPanelComponent implements OnInit, OnDestroy {
headerTitle: string;
headerSubtitle: string;
isReadOnly = false;
set entitiesTableConfig(entitiesTableConfig: EntityTableConfig<BaseData<HasId>>) {
if (this.entitiesTableConfigValue !== entitiesTableConfig) {
this.entitiesTableConfigValue = entitiesTableConfig;
if (this.entitiesTableConfigValue) {
this.isEdit = false;
this.entity = null;
}
}
}
get entitiesTableConfig(): EntityTableConfig<BaseData<HasId>> {
return this.entitiesTableConfigValue;
}
@HostBinding('class') 'tb-absolute-fill';
constructor(private route: ActivatedRoute,
private router: Router,
protected injector: Injector,
protected cd: ChangeDetectorRef,
protected componentFactoryResolver: ComponentFactoryResolver,
private broadcast: BroadcastService,
private translate: TranslateService,
protected store: Store<AppState>) {
super(store, injector, cd, componentFactoryResolver);
this.entitiesTableConfig = this.route.snapshot.data.entitiesTableConfig;
}
ngOnInit() {
this.headerSubtitle = '';
this.route.paramMap.subscribe( paramMap => {
this.entityId = new AssetId(paramMap.get('entityId'));
});
this.headerSubtitle = this.translate.instant(this.entitiesTableConfig.entityTranslations.details);
super.init();
this.entityComponent.isDetailsPage = true;
}
ngOnDestroy() {
super.ngOnDestroy();
}
reload(): void {
this.isEdit = false;
this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe(
(entity) => {
this.entity = entity;
this.broadcast.broadcast('updateBreadcrumb');
this.isReadOnly = this.entitiesTableConfig.detailsReadonly(entity);
this.headerTitle = this.entitiesTableConfig.entityTitle(entity);
this.entityComponent.entity = entity;
this.entityComponent.isEdit = false;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = entity;
}
}
);
}
onToggleDetailsEditMode() {
if (this.isEdit) {
this.entityComponent.entity = this.entity;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = this.entity;
}
this.isEdit = !this.isEdit;
} else {
this.isEdit = !this.isEdit;
this.editingEntity = deepClone(this.entity);
this.entityComponent.entity = this.editingEntity;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = this.editingEntity;
}
if (this.entitiesTableConfig.hideDetailsTabsOnEdit) {
this.selectedTab = 0;
}
}
}
onApplyDetails() {
if (this.detailsForm && this.detailsForm.valid) {
const editingEntity = {...this.editingEntity, ...this.detailsForm.getRawValue()};
if (this.detailsForm.hasOwnProperty('additionalInfo')) {
editingEntity.additionalInfo =
mergeDeep((this.editingEntity as any).additionalInfo, this.detailsForm.getRawValue()?.additionalInfo);
}
this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity).subscribe(
(entity) => {
this.entity = entity;
this.entityComponent.entity = entity;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = entity;
}
this.isEdit = false;
}
);
}
}
confirmForm(): FormGroup {
return this.detailsForm;
}
}

16
ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts

@ -88,15 +88,15 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
entity: BaseData<HasId>;
editingEntity: BaseData<HasId>;
private currentEntityId: HasId;
private subscriptions: Subscription[] = [];
private viewInited = false;
private pendingTabs: MatTab[];
protected currentEntityId: HasId;
protected subscriptions: Subscription[] = [];
protected viewInited = false;
protected pendingTabs: MatTab[];
constructor(protected store: Store<AppState>,
private injector: Injector,
private cd: ChangeDetectorRef,
private componentFactoryResolver: ComponentFactoryResolver) {
protected injector: Injector,
protected cd: ChangeDetectorRef,
protected componentFactoryResolver: ComponentFactoryResolver) {
super(store);
}
@ -139,7 +139,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
return this.isEditValue;
}
private init() {
protected init() {
this.translations = this.entitiesTableConfig.entityTranslations;
this.resources = this.entitiesTableConfig.entityResources;
this.buildEntityComponent();

2
ui-ngx/src/app/modules/home/components/entity/entity.component.ts

@ -38,6 +38,8 @@ export abstract class EntityComponent<T extends BaseData<HasId>,
isEditValue: boolean;
isDetailsPage = false;
@Input()
set entitiesTableConfig(entitiesTableConfig: C) {
this.setEntitiesTableConfig(entitiesTableConfig);

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

@ -147,6 +147,7 @@ import {
HOME_COMPONENTS_MODULE_TOKEN
} from '@home/components/tokens';
import { DashboardStateComponent } from '@home/components/dashboard-page/dashboard-state.component';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
@NgModule({
declarations:
@ -155,6 +156,7 @@ import { DashboardStateComponent } from '@home/components/dashboard-page/dashboa
AddEntityDialogComponent,
DetailsPanelComponent,
EntityDetailsPanelComponent,
EntityDetailsPageComponent,
AuditLogTableComponent,
AuditLogDetailsDialogComponent,
EventContentDialogComponent,
@ -282,6 +284,7 @@ import { DashboardStateComponent } from '@home/components/dashboard-page/dashboa
AddEntityDialogComponent,
DetailsPanelComponent,
EntityDetailsPanelComponent,
EntityDetailsPageComponent,
AuditLogTableComponent,
EventTableComponent,
EdgeDownlinkTableHeaderComponent,

7
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html

@ -38,6 +38,13 @@
(click)="editDeviceProfile($event)">
<mat-icon class="material-icons">edit</mat-icon>
</button>
<button *ngIf="showDetailsPageLink && selectDeviceProfileFormGroup.get('deviceProfile').value && disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Open device profile">
<a [routerLink]=deviceProfileURL>
<mat-icon class="material-icons">open_in_new</mat-icon>
</a>
</button>
<mat-autocomplete
class="tb-autocomplete"
(closed)="onPanelClosed()"

21
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.scss

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
.mat-icon-button a {
border-bottom: none;
color: inherit;
}
}

9
ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts

@ -47,11 +47,12 @@ import { DeviceProfileDialogComponent, DeviceProfileDialogData } from './device-
import { MatAutocomplete } from '@angular/material/autocomplete';
import { AddDeviceProfileDialogComponent, AddDeviceProfileDialogData } from './add-device-profile-dialog.component';
import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils';
@Component({
selector: 'tb-device-profile-autocomplete',
templateUrl: './device-profile-autocomplete.component.html',
styleUrls: [],
styleUrls: ['./device-profile-autocomplete.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DeviceProfileAutocompleteComponent),
@ -79,6 +80,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
@Input()
addNewProfile = true;
@Input()
showDetailsPageLink = false;
@Input()
transportType: DeviceTransportType = null;
@ -110,6 +114,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
filteredDeviceProfiles: Observable<Array<DeviceProfileInfo>>;
searchText = '';
deviceProfileURL: string;
private dirty = false;
@ -240,6 +245,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
this.deviceProfileService.getDeviceProfileInfo(value.id).subscribe(
(profile) => {
this.modelValue = new DeviceProfileId(profile.id.id);
this.deviceProfileURL = getEntityDetailsPageURL(this.modelValue.id, this.modelValue.entityType);
this.selectDeviceProfileFormGroup.get('deviceProfile').patchValue(profile, {emitEvent: false});
this.deviceProfileChanged.emit(profile);
}
@ -278,6 +284,7 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor,
const idValue = deviceProfile && deviceProfile.id ? new DeviceProfileId(deviceProfile.id.id) : null;
if (!entityIdEquals(this.modelValue, idValue)) {
this.modelValue = idValue;
this.deviceProfileURL = getEntityDetailsPageURL(this.modelValue.id, this.modelValue.entityType);
this.propagateChange(this.modelValue);
this.deviceProfileChanged.emit(deviceProfile);
}

2
ui-ngx/src/app/modules/home/components/profile/device-profile-provision-configuration.component.ts

@ -130,7 +130,7 @@ export class DeviceProfileProvisionConfigurationComponent implements ControlValu
setDisabledState(isDisabled: boolean){
this.disabled = isDisabled;
if (this.disabled){
this.provisionConfigurationFormGroup.disable();
this.provisionConfigurationFormGroup.disable({emitEvent: false});
} else {
if (this.provisionConfigurationFormGroup.get('type').value !== DeviceProvisionType.DISABLED) {
this.provisionConfigurationFormGroup.enable({emitEvent: false});

8
ui-ngx/src/app/modules/home/components/profile/device-profile.component.html

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'export')"
@ -73,12 +79,14 @@
</tb-queue-type-list>
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[showDetailsPageLink]="true"
[type]="otaUpdateType.FIRMWARE"
[deviceProfileId]="deviceProfileId?.id"
formControlName="firmwareId">
</tb-ota-package-autocomplete>
<tb-ota-package-autocomplete
[useFullEntityId]="true"
[showDetailsPageLink]="true"
[type]="otaUpdateType.SOFTWARE"
[deviceProfileId]="deviceProfileId?.id"
formControlName="softwareId">

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

@ -38,6 +38,13 @@
(click)="editTenantProfile($event)">
<mat-icon class="material-icons">edit</mat-icon>
</button>
<button *ngIf="showDetailsPageLink && selectTenantProfileFormGroup.get('tenantProfile').value && disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Open device profile">
<a [routerLink]=tenantProfileURL>
<mat-icon class="material-icons">open_in_new</mat-icon>
</a>
</button>
<mat-autocomplete
class="tb-autocomplete"
#tenantProfileAutocomplete="matAutocomplete"

21
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.scss

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
.mat-icon-button a {
border-bottom: none;
color: inherit;
}
}

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

@ -33,12 +33,13 @@ import { ENTER } from '@angular/cdk/keycodes';
import { TenantProfile } from '@shared/models/tenant.model';
import { MatDialog } from '@angular/material/dialog';
import { TenantProfileDialogComponent, TenantProfileDialogData } from './tenant-profile-dialog.component';
import { emptyPageData } from "@shared/models/page/page-data";
import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils';
@Component({
selector: 'tb-tenant-profile-autocomplete',
templateUrl: './tenant-profile-autocomplete.component.html',
styleUrls: [],
styleUrls: ['./tenant-profile-autocomplete.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => TenantProfileAutocompleteComponent),
@ -66,6 +67,9 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
@Input()
disabled: boolean;
@Input()
showDetailsPageLink = false;
@Output()
tenantProfileUpdated = new EventEmitter<TenantProfileId>();
@ -74,6 +78,7 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
filteredTenantProfiles: Observable<Array<EntityInfoData>>;
searchText = '';
tenantProfileURL: string;
private dirty = false;
@ -123,6 +128,7 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
(profile) => {
if (profile) {
this.modelValue = new TenantProfileId(profile.id.id);
this.tenantProfileURL = getEntityDetailsPageURL(this.modelValue.id, this.modelValue.entityType);
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: false});
this.propagateChange(this.modelValue);
}
@ -133,6 +139,11 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (this.disabled) {
this.selectTenantProfileFormGroup.disable();
} else {
this.selectTenantProfileFormGroup.enable();
}
}
writeValue(value: TenantProfileId | null): void {
@ -141,6 +152,7 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
this.tenantProfileService.getTenantProfileInfo(value.id).subscribe(
(profile) => {
this.modelValue = new TenantProfileId(profile.id.id);
this.tenantProfileURL = getEntityDetailsPageURL(this.modelValue.id, this.modelValue.entityType);
this.selectTenantProfileFormGroup.get('tenantProfile').patchValue(profile, {emitEvent: false});
}
);

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

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column" *ngIf="!standalone">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'setDefault')"

8
ui-ngx/src/app/modules/home/components/relation/relation-table.component.html

@ -117,7 +117,9 @@
<ng-container *ngIf="direction === directions.FROM" matColumnDef="toName">
<mat-header-cell *matHeaderCellDef mat-sort-header style="width: 33%"> {{ 'relation.to-entity-name' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let relation">
{{ relation.toName }}
<a class="link" [routerLink]="relation.entityURL">
{{ relation.toName }}
</a>
</mat-cell>
</ng-container>
<ng-container *ngIf="direction === directions.TO" matColumnDef="fromEntityTypeName">
@ -129,7 +131,9 @@
<ng-container *ngIf="direction === directions.TO" matColumnDef="fromName">
<mat-header-cell *matHeaderCellDef mat-sort-header style="width: 33%"> {{ 'relation.from-entity-name' | translate }} </mat-header-cell>
<mat-cell *matCellDef="let relation">
{{ relation.fromName }}
<a class="link" [routerLink]="relation.entityURL">
{{ relation.fromName }}
</a>
</mat-cell>
</ng-container>
<ng-container matColumnDef="actions" stickyEnd>

3
ui-ngx/src/app/modules/home/components/relation/relation-table.component.scss

@ -41,6 +41,9 @@
.table-container {
overflow: auto;
.link {
border-bottom: none;
}
}
.tb-entity-table-info{

5
ui-ngx/src/app/modules/home/models/datasource/relation-datasource.ts

@ -23,7 +23,8 @@ import { PageLink } from '@shared/models/page/page-link';
import { catchError, map, publishReplay, refCount, take, tap } from 'rxjs/operators';
import { EntityId } from '@app/shared/models/id/entity-id';
import { TranslateService } from '@ngx-translate/core';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models';
import { getEntityDetailsPageURL } from '@core/utils';
export class RelationsDatasource implements DataSource<EntityRelationInfo> {
@ -92,8 +93,10 @@ export class RelationsDatasource implements DataSource<EntityRelationInfo> {
relations.forEach(relation => {
if (direction === EntitySearchDirection.FROM) {
relation.toEntityTypeName = this.translate.instant(entityTypeTranslations.get(relation.to.entityType).type);
relation.entityURL = getEntityDetailsPageURL(relation.to.id, relation.to.entityType as EntityType);
} else {
relation.fromEntityTypeName = this.translate.instant(entityTypeTranslations.get(relation.from.entityType).type);
relation.entityURL = getEntityDetailsPageURL(relation.from.id, relation.from.entityType as EntityType);
}
});
return relations;

38
ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts

@ -29,6 +29,9 @@ import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component';
import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { ResourcesLibraryTableConfigResolver } from '@home/pages/admin/resource/resources-library-table-config.resolve';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
@Injectable()
export class OAuth2LoginProcessingUrlResolver implements Resolve<string> {
@ -145,18 +148,41 @@ const routes: Routes = [
},
{
path: 'resources-library',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'resource.resources-library',
breadcrumb: {
label: 'resource.resources-library',
icon: 'folder'
}
},
resolve: {
entitiesTableConfig: ResourcesLibraryTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'resource.resources-library',
},
resolve: {
entitiesTableConfig: ResourcesLibraryTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'folder'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN],
title: 'resource.resources-library'
},
resolve: {
entitiesTableConfig: ResourcesLibraryTableConfigResolver
}
}
]
}
]
}

13
ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts

@ -21,7 +21,7 @@ import {
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { Resolve } from '@angular/router';
import { Resolve, Router } from '@angular/router';
import { Resource, ResourceInfo, ResourceTypeTranslationMap } from '@shared/models/resource.models';
import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
@ -46,6 +46,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
constructor(private store: Store<AppState>,
private resourceService: ResourceService,
private translate: TranslateService,
private router: Router,
private datePipe: DatePipe) {
this.config.entityType = EntityType.TB_RESOURCE;
@ -118,6 +119,13 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
return this.config;
}
private openResource($event: Event, resourceInfo: ResourceInfo) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`settings/resources-library/${resourceInfo.id.id}`);
}
downloadResource($event: Event, resource: ResourceInfo) {
if ($event) {
$event.stopPropagation();
@ -127,6 +135,9 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
onResourceAction(action: EntityAction<ResourceInfo>): boolean {
switch (action.action) {
case 'open':
this.openResource(action.event, action.entity);
return true;
case 'downloadResource':
this.downloadResource(action.event, action.entity);
return true;

6
ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'downloadResource')"

5
ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts

@ -64,7 +64,7 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
this.entityForm.get('title').disable({emitEvent: false});
this.entityForm.patchValue({title: ''}, {emitEvent: false});
} else {
this.entityForm.get('title').enable({emitEvent: false})
this.entityForm.get('title').enable({emitEvent: false});
}
this.entityForm.patchValue({
data: null,
@ -90,7 +90,7 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
buildForm(entity: Resource): FormGroup {
const form = this.fb.group(
{
title: [entity ? entity.title : "", [Validators.required, Validators.maxLength(255)]],
title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]],
resourceType: [entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, [Validators.required]],
fileName: [entity ? entity.fileName : null, [Validators.required]],
}
@ -102,6 +102,7 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
}
updateForm(entity: Resource) {
this.entity.name = entity.title;
if (this.isEdit) {
this.entityForm.get('resourceType').disable({emitEvent: false});
this.entityForm.get('fileName').disable({emitEvent: false});

42
ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts

@ -20,23 +20,51 @@ import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { AssetsTableConfigResolver } from './assets-table-config.resolver';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
const routes: Routes = [
{
path: 'assets',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'asset.assets',
assetsType: 'tenant',
breadcrumb: {
label: 'asset.assets',
icon: 'domain'
}
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
title: 'asset.assets',
assetsType: 'tenant'
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'domain'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'asset.assets',
assetsType: 'tenant'
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
}
]
}
];

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

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'makePublic')"

10
ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts

@ -337,6 +337,13 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
});
}
private openAsset($event: Event, asset: Asset) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${asset.id.id}`);
}
addAssetsToCustomer($event: Event) {
if ($event) {
$event.stopPropagation();
@ -461,6 +468,9 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
onAssetAction(action: EntityAction<AssetInfo>): boolean {
switch (action.action) {
case 'open':
this.openAsset(action.event, action.entity);
return true;
case 'makePublic':
this.makePublic(action.event, action.entity);
return true;

167
ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts

@ -28,6 +28,9 @@ import { DashboardPageComponent } from '@home/components/dashboard-page/dashboar
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
import { dashboardBreadcumbLabelFunction, DashboardResolver } from '@home/pages/dashboard/dashboard-routing.module';
import { EdgesTableConfigResolver } from '@home/pages/edge/edges-table-config.resolver';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
const routes: Routes = [
{
@ -51,67 +54,177 @@ const routes: Routes = [
}
},
{
path: ':customerId/users',
component: EntitiesTableComponent,
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'supervisor_account'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'user.customer-users',
title: 'customer.customers'
},
resolve: {
entitiesTableConfig: CustomersTableConfigResolver
}
},
{
path: ':customerId/users',
data: {
breadcrumb: {
label: 'user.customer-users',
icon: 'account_circle'
}
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'user.customer-users'
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'account_circle'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'user.customer-users'
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
}
]
},
{
path: ':customerId/devices',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'customer.devices',
devicesType: 'customer',
breadcrumb: {
label: 'customer.devices',
icon: 'devices_other'
}
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'customer.devices',
devicesType: 'customer'
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'devices_other'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'customer.devices',
devicesType: 'customer'
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
}
]
},
{
path: ':customerId/assets',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'customer.assets',
assetsType: 'customer',
breadcrumb: {
label: 'customer.assets',
icon: 'domain'
}
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'customer.assets',
assetsType: 'customer'
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'domain'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'customer.assets'
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
}
]
},
{
path: ':customerId/edgeInstances',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'customer.edges',
edgesType: 'customer',
breadcrumb: {
label: 'customer.edges',
icon: 'router'
}
},
resolve: {
entitiesTableConfig: EdgesTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'customer.edges',
edgesType: 'customer'
},
resolve: {
entitiesTableConfig: EdgesTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'router'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'customer.edges',
edgesType: 'customer'
},
resolve: {
entitiesTableConfig: EdgesTableConfigResolver
}
}
]
},
{
path: ':customerId/dashboards',

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

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageUsers')"

10
ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts

@ -137,6 +137,13 @@ export class CustomersTableConfigResolver implements Resolve<EntityTableConfig<C
return this.config;
}
private openCustomer($event: Event, customer: Customer) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${customer.id.id}`);
}
manageCustomerUsers($event: Event, customer: Customer) {
if ($event) {
$event.stopPropagation();
@ -174,6 +181,9 @@ export class CustomersTableConfigResolver implements Resolve<EntityTableConfig<C
onCustomerAction(action: EntityAction<Customer>): boolean {
switch (action.action) {
case 'open':
this.openCustomer(action.event, action.entity);
return true;
case 'manageUsers':
this.manageCustomerUsers(action.event, action.entity);
return true;

20
ui-ngx/src/app/modules/home/pages/device-profile/device-profile-routing.module.ts

@ -20,6 +20,10 @@ import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { DeviceProfilesTableConfigResolver } from './device-profiles-table-config.resolver';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
@ -41,6 +45,22 @@ const routes: Routes = [
resolve: {
entitiesTableConfig: DeviceProfilesTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'mdi:alpha-d-box'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'device-profile.device-profiles'
},
resolve: {
entitiesTableConfig: DeviceProfilesTableConfigResolver
}
}
]
}

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

@ -15,7 +15,7 @@
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Resolve, Router } from '@angular/router';
import {
checkBoxCell,
DateEntityTableColumn,
@ -53,6 +53,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
private translate: TranslateService,
private datePipe: DatePipe,
private dialogService: DialogService,
private router: Router,
private dialog: MatDialog) {
this.config.entityType = EntityType.DEVICE_PROFILE;
@ -178,6 +179,13 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
);
}
private openDeviceProfile($event: Event, deviceProfile: DeviceProfile) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`deviceProfiles/${deviceProfile.id.id}`);
}
importDeviceProfile($event: Event) {
this.importExport.importDeviceProfile().subscribe(
(deviceProfile) => {
@ -197,6 +205,9 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
onDeviceProfileAction(action: EntityAction<DeviceProfile>): boolean {
switch (action.action) {
case 'open':
this.openDeviceProfile(action.event, action.entity);
return true;
case 'setDefault':
this.setDefaultDeviceProfile(action.event, action.entity);
return true;

42
ui-ngx/src/app/modules/home/pages/device/device-routing.module.ts

@ -20,23 +20,51 @@ import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { DevicesTableConfigResolver } from '@modules/home/pages/device/devices-table-config.resolver';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
path: 'devices',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'device.devices',
devicesType: 'tenant',
breadcrumb: {
label: 'device.devices',
icon: 'devices_other'
}
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'device.devices',
devicesType: 'tenant'
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'devices_other'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'device.devices',
devicesType: 'tenant'
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
}
]
}
];

7
ui-ngx/src/app/modules/home/pages/device/device.component.html

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'makePublic')"
@ -97,6 +103,7 @@
[selectDefaultProfile]="isAdd"
required
formControlName="deviceProfileId"
[showDetailsPageLink]="true"
(deviceProfileUpdated)="onDeviceProfileUpdated()"
(deviceProfileChanged)="onDeviceProfileChanged($event)">
</tb-device-profile-autocomplete>

1
ui-ngx/src/app/modules/home/pages/device/device.component.ts

@ -36,6 +36,7 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod
import { Subject } from 'rxjs';
import { OtaUpdateType } from '@shared/models/ota-package.models';
import { distinctUntilChanged } from 'rxjs/operators';
import { getEntityDetailsPageURL } from '@core/utils';
@Component({
selector: 'tb-device',

10
ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts

@ -371,6 +371,13 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
return actions;
}
private openDevice($event: Event, device: Device) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${device.id.id}`);
}
importDevices($event: Event) {
this.homeDialogs.importEntities(EntityType.DEVICE).subscribe((res) => {
if (res) {
@ -541,6 +548,9 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
onDeviceAction(action: EntityAction<DeviceInfo>): boolean {
switch (action.action) {
case 'open':
this.openDevice(action.event, action.entity);
return true;
case 'makePublic':
this.makePublic(action.event, action.entity);
return true;

131
ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts

@ -39,6 +39,8 @@ import {
RuleNodeComponentsResolver,
TooltipsterResolver
} from '@home/pages/rulechain/rulechain-routing.module';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
const routes: Routes = [
{
@ -55,6 +57,7 @@ const routes: Routes = [
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.edge-instances',
edgesType: 'tenant'
},
resolve: {
@ -62,49 +65,141 @@ const routes: Routes = [
}
},
{
path: ':edgeId/assets',
component: EntitiesTableComponent,
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'router'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
assetsType: 'edge',
title: 'edge.edge-instances',
edgesType: 'tenant'
},
resolve: {
entitiesTableConfig: EdgesTableConfigResolver
}
},
{
path: ':edgeId/assets',
data: {
breadcrumb: {
label: 'edge.assets',
icon: 'domain'
}
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.assets',
assetsType: 'edge'
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'domain'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.assets',
assetsType: 'edge'
},
resolve: {
entitiesTableConfig: AssetsTableConfigResolver
}
}
]
},
{
path: ':edgeId/devices',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
devicesType: 'edge',
breadcrumb: {
label: 'edge.devices',
icon: 'devices_other'
}
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.devices',
devicesType: 'edge'
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'devices_other'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.devices',
devicesType: 'edge'
},
resolve: {
entitiesTableConfig: DevicesTableConfigResolver
}
}
]
},
{
path: ':edgeId/entityViews',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
entityViewsType: 'edge',
breadcrumb: {
label: 'edge.entity-views',
icon: 'view_quilt'
},
},
resolve: {
entitiesTableConfig: EntityViewsTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.entity-views',
entityViewsType: 'edge'
},
resolve: {
entitiesTableConfig: EntityViewsTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'devices_other'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'edge.entity-views',
entityViewsType: 'edge'
},
resolve: {
entitiesTableConfig: EntityViewsTableConfigResolver
}
}
]
},
{
path: ':edgeId/dashboards',

6
ui-ngx/src/app/modules/home/pages/edge/edge.component.html

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'makePublic')"

10
ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts

@ -370,6 +370,13 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
});
}
private openEdge($event: Event, edge: Edge) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${edge.id.id}`);
}
makePublic($event: Event, edge: Edge) {
if ($event) {
$event.stopPropagation();
@ -520,6 +527,9 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
onEdgeAction(action: EntityAction<EdgeInfo>): boolean {
switch (action.action) {
case 'open':
this.openEdge(action.event, action.entity);
return true;
case 'makePublic':
this.makePublic(action.event, action.entity);
return true;

42
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-routing.module.ts

@ -20,23 +20,51 @@ import { RouterModule, Routes } from '@angular/router';
import { EntitiesTableComponent } from '../../components/entity/entities-table.component';
import { Authority } from '@shared/models/authority.enum';
import { EntityViewsTableConfigResolver } from '@modules/home/pages/entity-view/entity-views-table-config.resolver';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
path: 'entityViews',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'entity-view.entity-views',
entityViewsType: 'tenant',
breadcrumb: {
label: 'entity-view.entity-views',
icon: 'view_quilt'
}
},
resolve: {
entitiesTableConfig: EntityViewsTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'entity-view.entity-views',
entityViewsType: 'tenant'
},
resolve: {
entitiesTableConfig: EntityViewsTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'view_quilt'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER],
title: 'entity-view.entity-views',
entityViewsType: 'tenant'
},
resolve: {
entitiesTableConfig: EntityViewsTableConfigResolver
}
}
]
}
];

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

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'makePublic')"

10
ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts

@ -333,6 +333,13 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
});
}
private openEntityView($event: Event, entityView: EntityView) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${entityView.id.id}`);
}
makePublic($event: Event, entityView: EntityView) {
if ($event) {
$event.stopPropagation();
@ -437,6 +444,9 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
onEntityViewAction(action: EntityAction<EntityViewInfo>): boolean {
switch (action.action) {
case 'open':
this.openEntityView(action.event, action.entity);
return true;
case 'makePublic':
this.makePublic(action.event, action.entity);
return true;

39
ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts

@ -19,22 +19,49 @@ import { EntitiesTableComponent } from '@home/components/entity/entities-table.c
import { Authority } from '@shared/models/authority.enum';
import { NgModule } from '@angular/core';
import { OtaUpdateTableConfigResolve } from '@home/pages/ota-update/ota-update-table-config.resolve';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
path: 'otaUpdates',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'ota-update.ota-updates',
breadcrumb: {
label: 'ota-update.ota-updates',
icon: 'memory'
}
},
resolve: {
entitiesTableConfig: OtaUpdateTableConfigResolve
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.TENANT_ADMIN],
title: 'ota-update.ota-updates'
},
resolve: {
entitiesTableConfig: OtaUpdateTableConfigResolve
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'memory'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.TENANT_ADMIN],
title: 'ota-update.ota-updates'
},
resolve: {
entitiesTableConfig: OtaUpdateTableConfigResolve
}
}
]
}
];

13
ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts

@ -15,7 +15,7 @@
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { Resolve, Router } from '@angular/router';
import {
CellActionDescriptorType,
DateEntityTableColumn,
@ -49,6 +49,7 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
private datePipe: DatePipe,
private store: Store<AppState>,
private otaPackageService: OtaPackageService,
private router: Router,
private fileSize: FileSizePipe) {
this.config.entityType = EntityType.OTA_PACKAGE;
this.config.entityComponent = OtaUpdateComponent;
@ -127,6 +128,13 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
return this.config;
}
private openOtaPackage($event: Event, otaPackage: OtaPackageInfo) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`otaUpdates/${otaPackage.id.id}`);
}
exportPackage($event: Event, otaPackageInfo: OtaPackageInfo) {
if ($event) {
$event.stopPropagation();
@ -148,6 +156,9 @@ export class OtaUpdateTableConfigResolve implements Resolve<EntityTableConfig<Ot
onPackageAction(action: EntityAction<OtaPackageInfo>): boolean {
switch (action.action) {
case 'open':
this.openOtaPackage(action.event, action.entity);
return true;
case 'uploadPackage':
this.exportPackage(action.event, action.entity);
return true;

9
ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary" fxFlex.xs
[disabled]="(isLoading$ | async) || !(entity?.hasData && !entity?.url)"
(click)="onEntityAction($event, 'uploadPackage')"
@ -92,7 +98,8 @@
[hint]="'ota-update.chose-compatible-device-profile'"
[editProfileEnabled]="false"
[addNewProfile]="false"
[selectDefaultProfile]="true">
[showDetailsPageLink]="true"
[selectDefaultProfile]="isAdd">
</tb-device-profile-autocomplete>
<mat-form-field class="mat-block">
<mat-label translate>ota-update.package-type</mat-label>

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

@ -20,6 +20,10 @@ 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';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
@ -41,6 +45,22 @@ const routes: Routes = [
resolve: {
entitiesTableConfig: TenantProfilesTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'mdi:alpha-t-box'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.SYS_ADMIN],
title: 'tenant-profile.tenant-profiles'
},
resolve: {
entitiesTableConfig: TenantProfilesTableConfigResolver
}
}
]
}

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

@ -15,8 +15,8 @@
///
import { Injectable } from '@angular/core';
import { Resolve } from '@angular/router';
import { TenantProfile } from '@shared/models/tenant.model';
import { Resolve, Router } from '@angular/router';
import { TenantInfo, TenantProfile } from '@shared/models/tenant.model';
import {
checkBoxCell,
DateEntityTableColumn,
@ -43,6 +43,7 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
private importExport: ImportExportService,
private translate: TranslateService,
private datePipe: DatePipe,
private router: Router,
private dialogService: DialogService) {
this.config.entityType = EntityType.TENANT_PROFILE;
@ -117,6 +118,13 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
return actions;
}
private openTenantProfile($event: Event, tenantProfile: TenantProfile) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`tenantProfiles/${tenantProfile.id.id}`);
}
setDefaultTenantProfile($event: Event, tenantProfile: TenantProfile) {
if ($event) {
$event.stopPropagation();
@ -158,6 +166,9 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
onTenantProfileAction(action: EntityAction<TenantProfile>): boolean {
switch (action.action) {
case 'open':
this.openTenantProfile(action.event, action.entity);
return true;
case 'setDefault':
this.setDefaultTenantProfile(action.event, action.entity);
return true;

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

@ -21,6 +21,10 @@ import { EntitiesTableComponent } from '../../components/entity/entities-table.c
import { Authority } from '@shared/models/authority.enum';
import { TenantsTableConfigResolver } from '@modules/home/pages/tenant/tenants-table-config.resolver';
import { UsersTableConfigResolver } from '../user/users-table-config.resolver';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
@ -44,19 +48,58 @@ const routes: Routes = [
}
},
{
path: ':tenantId/users',
component: EntitiesTableComponent,
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'supervisor_account'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.SYS_ADMIN],
title: 'user.tenant-admins',
title: 'tenant.tenants'
},
resolve: {
entitiesTableConfig: TenantsTableConfigResolver
}
},
{
path: ':tenantId/users',
data: {
breadcrumb: {
label: 'user.tenant-admins',
icon: 'account_circle'
}
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
children: [
{
path: '',
component: EntitiesTableComponent,
data: {
auth: [Authority.SYS_ADMIN],
title: 'user.tenant-admins'
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'account_circle'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.SYS_ADMIN],
title: 'user.tenant-admins'
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
}
]
}
]
}

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

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageTenantAdmins')"
@ -54,6 +60,7 @@
</mat-form-field>
<tb-tenant-profile-autocomplete
[selectDefaultProfile]="isAdd"
[showDetailsPageLink]="true"
required
formControlName="tenantProfileId"
(tenantProfileUpdated)="onTenantProfileUpdated()">

10
ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts

@ -87,6 +87,13 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten
return this.config;
}
private openTenant($event: Event, tenant: TenantInfo) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${tenant.id.id}`);
}
manageTenantAdmins($event: Event, tenant: TenantInfo) {
if ($event) {
$event.stopPropagation();
@ -96,6 +103,9 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten
onTenantAction(action: EntityAction<TenantInfo>): boolean {
switch (action.action) {
case 'open':
this.openTenant(action.event, action.entity);
return true;
case 'manageTenantAdmins':
this.manageTenantAdmins(action.event, action.entity);
return true;

43
ui-ngx/src/app/modules/home/pages/user/user-routing.module.ts

@ -15,11 +15,50 @@
///
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { RouterModule, Routes } from '@angular/router';
import { UsersTableConfigResolver } from '@modules/home/pages/user/users-table-config.resolver';
import { Authority } from '@shared/models/authority.enum';
import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component';
import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard';
import { entityDetailsPageBreadcrumbLabelFunction } from '@core/utils';
import { BreadCrumbConfig } from '@shared/components/breadcrumb';
const routes: Routes = [
{
path: 'users',
data: {
breadcrumb: {
skip: true
}
},
children: [
{
path: '',
redirectTo: '/',
pathMatch: 'full'
},
{
path: ':entityId',
component: EntityDetailsPageComponent,
canDeactivate: [ConfirmOnExitGuard],
data: {
breadcrumb: {
labelFunction: entityDetailsPageBreadcrumbLabelFunction,
icon: 'account_circle'
} as BreadCrumbConfig<EntityDetailsPageComponent>,
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
title: 'user.user',
},
resolve: {
entitiesTableConfig: UsersTableConfigResolver
}
}
]
}
];
@NgModule({
imports: [],
imports: [RouterModule.forChild(routes)],
exports: [RouterModule],
providers: [
UsersTableConfigResolver

6
ui-ngx/src/app/modules/home/pages/user/user.component.html

@ -16,6 +16,12 @@
-->
<div class="tb-details-buttons" fxLayout.xs="column">
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'open')"
[fxShow]="!isEdit && !isDetailsPage">
{{'common.open-details-page' | translate }}
</button>
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'disableAccount')"

27
ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts

@ -16,7 +16,7 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { ActivatedRouteSnapshot, Resolve, Router } from '@angular/router';
import {
DateEntityTableColumn,
EntityTableColumn,
@ -30,7 +30,7 @@ import { UserService } from '@core/http/user.service';
import { UserComponent } from '@modules/home/pages/user/user.component';
import { CustomerService } from '@core/http/customer.service';
import { map, mergeMap, take, tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Observable, of } from 'rxjs';
import { Authority } from '@shared/models/authority.enum';
import { CustomerId } from '@shared/models/id/customer-id';
import { MatDialog } from '@angular/material/dialog';
@ -50,6 +50,7 @@ import { NULL_UUID } from '@shared/models/id/has-uuid';
import { TenantService } from '@app/core/http/tenant.service';
import { TenantId } from '@app/shared/models/id/tenant-id';
import { UserTabsComponent } from '@home/pages/user/user-tabs.component';
import { isDefinedAndNotNull } from '@core/utils';
export interface UsersTableRouteData {
authority: Authority;
@ -72,6 +73,7 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
private customerService: CustomerService,
private translate: TranslateService,
private datePipe: DatePipe,
private router: Router,
private dialog: MatDialog) {
this.config.entityType = EntityType.USER;
@ -117,9 +119,14 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
}
this.updateActionCellDescriptors(auth);
}),
mergeMap(() => this.authority === Authority.TENANT_ADMIN ?
this.tenantService.getTenant(this.tenantId) :
this.customerService.getCustomer(this.customerId)),
mergeMap(() => {
if (this.authority === Authority.TENANT_ADMIN) {
return this.tenantService.getTenant(this.tenantId);
} else if (isDefinedAndNotNull(this.customerId)) {
return this.customerService.getCustomer(this.customerId);
}
return of({title: ''});
}),
map((parentEntity) => {
if (this.authority === Authority.TENANT_ADMIN) {
this.config.tableTitle = parentEntity.title + ': ' + this.translate.instant('user.tenant-admins');
@ -167,6 +174,13 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
}).afterClosed();
}
private openUser($event: Event, user: User) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(`${this.router.url}/${user.id.id}`);
}
loginAsUser($event: Event, user: User) {
if ($event) {
$event.stopPropagation();
@ -224,6 +238,9 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
onUserAction(action: EntityAction<User>): boolean {
switch (action.action) {
case 'open':
this.openUser(action.event, action.entity);
return true;
case 'loginAsUser':
this.loginAsUser(action.event, action.entity);
return true;

8
ui-ngx/src/app/shared/components/breadcrumb.component.ts

@ -14,13 +14,14 @@
/// limitations under the License.
///
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from '@angular/core';
import { BehaviorSubject, Subject } from 'rxjs';
import { BreadCrumb, BreadCrumbConfig } from './breadcrumb';
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Router } from '@angular/router';
import { distinctUntilChanged, filter, map } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core';
import { guid } from '@core/utils';
import { BroadcastService } from '@core/services/broadcast.service';
@Component({
selector: 'tb-breadcrumb',
@ -51,10 +52,15 @@ export class BreadcrumbComponent implements OnInit, OnDestroy {
constructor(private router: Router,
private activatedRoute: ActivatedRoute,
private broadcast: BroadcastService,
private cd: ChangeDetectorRef,
private translate: TranslateService) {
}
ngOnInit(): void {
this.broadcast.on('updateBreadcrumb', () => {
this.cd.markForCheck();
});
}
ngOnDestroy(): void {

7
ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html

@ -29,6 +29,13 @@
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<button *ngIf="showDetailsPageLink && otaPackageFormGroup.get('packageId').value && disabled"
type="button"
matSuffix mat-button mat-icon-button aria-label="Open ota package">
<a [routerLink]=packageURL>
<mat-icon class="material-icons">open_in_new</mat-icon>
</a>
</button>
<mat-autocomplete class="tb-autocomplete"
#packageAutocomplete="matAutocomplete"
[displayWith]="displayPackageFn">

21
ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.scss

@ -0,0 +1,21 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host{
.mat-icon-button a {
border-bottom: none;
color: inherit;
}
}

10
ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts

@ -31,12 +31,13 @@ import { OtaPackageInfo, OtaUpdateTranslation, OtaUpdateType } from '@shared/mod
import { OtaPackageService } from '@core/http/ota-package.service';
import { PageLink } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { emptyPageData } from "@shared/models/page/page-data";
import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils';
@Component({
selector: 'tb-ota-package-autocomplete',
templateUrl: './ota-package-autocomplete.component.html',
styleUrls: [],
styleUrls: ['./ota-package-autocomplete.component.scss'],
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => OtaPackageAutocompleteComponent),
@ -64,6 +65,9 @@ export class OtaPackageAutocompleteComponent implements ControlValueAccessor, On
@Input()
useFullEntityId = false;
@Input()
showDetailsPageLink = false;
private requiredValue: boolean;
get required(): boolean {
@ -83,6 +87,7 @@ export class OtaPackageAutocompleteComponent implements ControlValueAccessor, On
filteredPackages: Observable<Array<OtaPackageInfo>>;
searchText = '';
packageURL: string;
private dirty = false;
@ -166,6 +171,7 @@ export class OtaPackageAutocompleteComponent implements ControlValueAccessor, On
if (packageId !== '') {
this.entityService.getEntity(EntityType.OTA_PACKAGE, packageId, {ignoreLoading: true, ignoreErrors: true}).subscribe(
(entity) => {
this.packageURL = getEntityDetailsPageURL(entity.id.id, EntityType.OTA_PACKAGE);
this.modelValue = this.useFullEntityId ? entity.id : entity.id.id;
this.otaPackageFormGroup.get('packageId').patchValue(entity, {emitEvent: false});
},

15
ui-ngx/src/app/shared/models/constants.ts

@ -16,6 +16,7 @@
import { InjectionToken } from '@angular/core';
import { IModulesMap } from '@modules/common/modules-map.models';
import { AliasEntityType, EntityType } from '@shared/models/entity-type.models';
export const Constants = {
serverErrorCode: {
@ -137,6 +138,20 @@ export const HelpLinks = {
}
};
export const baseDetailsPageByEntityType = new Map<EntityType, string>([
[EntityType.TENANT, '/tenants'],
[EntityType.TENANT_PROFILE, '/tenantProfiles'],
[EntityType.CUSTOMER, '/customers'],
[EntityType.USER, '/users'],
[EntityType.ASSET, '/assets'],
[EntityType.DEVICE, '/devices'],
[EntityType.DEVICE_PROFILE, '/deviceProfiles'],
[EntityType.EDGE, '/edgeInstances'],
[EntityType.ENTITY_VIEW, '/entityViews'],
[EntityType.TB_RESOURCE, '/settings/resources-library'],
[EntityType.OTA_PACKAGE, '/otaUpdates']
]);
export interface ValueTypeData {
name: string;
icon: string;

1
ui-ngx/src/app/shared/models/relation.models.ts

@ -89,4 +89,5 @@ export interface EntityRelationInfo extends EntityRelation {
toEntityTypeName?: string;
toName: string;
fromEntityTypeName?: string;
entityURL?: string;
}

1
ui-ngx/src/app/shared/shared.module.ts

@ -455,6 +455,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
KeyboardShortcutPipe,
FileSizePipe,
SelectableColumnsPipe,
RouterModule,
TranslateModule,
JsonObjectEditDialogComponent,
HistorySelectorComponent,

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

@ -622,7 +622,8 @@
"enter-search": "Enter search",
"created-time": "Created time",
"loading": "Loading...",
"proceed": "Proceed"
"proceed": "Proceed",
"open-details-page": "Open details page"
},
"content-type": {
"json": "Json",

Loading…
Cancel
Save