42 changed files with 1663 additions and 78 deletions
@ -0,0 +1,67 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { AssetProfile, AssetProfileInfo } from '@shared/models/asset.models'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
providedIn: 'root' |
||||
|
}) |
||||
|
export class AssetProfileService { |
||||
|
|
||||
|
constructor( |
||||
|
private http: HttpClient |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public getAssetProfiles(pageLink: PageLink, config?: RequestConfig): Observable<PageData<AssetProfile>> { |
||||
|
return this.http.get<PageData<AssetProfile>>(`/api/assetProfiles${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public getAssetProfile(assetProfileId: string, config?: RequestConfig): Observable<AssetProfile> { |
||||
|
return this.http.get<AssetProfile>(`/api/assetProfile/${assetProfileId}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public saveAssetProfile(assetProfile: AssetProfile, config?: RequestConfig): Observable<AssetProfile> { |
||||
|
return this.http.post<AssetProfile>('/api/assetProfile', assetProfile, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public deleteAssetProfile(assetProfileId: string, config?: RequestConfig) { |
||||
|
return this.http.delete(`/api/assetProfile/${assetProfileId}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public setDefaultAssetProfile(assetProfileId: string, config?: RequestConfig): Observable<AssetProfile> { |
||||
|
return this.http.post<AssetProfile>(`/api/assetProfile/${assetProfileId}/default`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public getDefaultAssetProfileInfo(config?: RequestConfig): Observable<AssetProfileInfo> { |
||||
|
return this.http.get<AssetProfileInfo>('/api/assetProfileInfo/default', defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public getAssetProfileInfo(assetProfileId: string, config?: RequestConfig): Observable<AssetProfileInfo> { |
||||
|
return this.http.get<AssetProfileInfo>(`/api/assetProfileInfo/${assetProfileId}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
public getAssetProfileInfos(pageLink: PageLink, config?: RequestConfig): Observable<PageData<AssetProfileInfo>> { |
||||
|
return this.http.get<PageData<AssetProfileInfo>>(`/api/assetProfileInfos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2022 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]="selectAssetProfileFormGroup" class="mat-block"> |
||||
|
<input matInput type="text" placeholder="{{ 'asset-profile.asset-profile' | translate }}" |
||||
|
#assetProfileInput |
||||
|
formControlName="assetProfile" |
||||
|
(focusin)="onFocus()" |
||||
|
[required]="required" |
||||
|
(keydown)="assetProfileEnter($event)" |
||||
|
(keypress)="assetProfileEnter($event)" |
||||
|
[matAutocomplete]="assetProfileAutocomplete" |
||||
|
[fxShow]="!showDetailsPageLink || !disabled || !selectAssetProfileFormGroup.get('assetProfile').value"> |
||||
|
<a *ngIf="showDetailsPageLink && selectAssetProfileFormGroup.get('assetProfile').value && disabled" aria-label="Open asset profile" [routerLink]=assetProfileURL> |
||||
|
{{ displayAssetProfileFn(selectAssetProfileFormGroup.get('assetProfile').value) }} |
||||
|
</a> |
||||
|
<button *ngIf="selectAssetProfileFormGroup.get('assetProfile').value && !disabled" |
||||
|
type="button" |
||||
|
matSuffix mat-button mat-icon-button aria-label="Clear" |
||||
|
(click)="clear()"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
<button *ngIf="selectAssetProfileFormGroup.get('assetProfile').value && !disabled && editProfileEnabled" |
||||
|
type="button" |
||||
|
matSuffix mat-button mat-icon-button aria-label="Edit" |
||||
|
matTooltip="{{ 'asset-profile.edit' | translate }}" |
||||
|
matTooltipPosition="above" |
||||
|
(click)="editAssetProfile($event)"> |
||||
|
<mat-icon class="material-icons">edit</mat-icon> |
||||
|
</button> |
||||
|
<mat-autocomplete |
||||
|
class="tb-autocomplete" |
||||
|
(closed)="onPanelClosed()" |
||||
|
#assetProfileAutocomplete="matAutocomplete" |
||||
|
[displayWith]="displayAssetProfileFn"> |
||||
|
<mat-option *ngFor="let assetProfile of filteredAssetProfiles | async" [value]="assetProfile"> |
||||
|
<span [innerHTML]="assetProfile.name | highlight:searchText"></span> |
||||
|
</mat-option> |
||||
|
<mat-option *ngIf="!(filteredAssetProfiles | async)?.length" [value]="null" class="tb-not-found"> |
||||
|
<div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
||||
|
<div *ngIf="!textIsNotEmpty(searchText) || !addNewProfile; else searchNotEmpty"> |
||||
|
<span translate>asset-profile.no-asset-profiles-found</span> |
||||
|
</div> |
||||
|
<ng-template #searchNotEmpty> |
||||
|
<span> |
||||
|
{{ translate.get('asset-profile.no-asset-profiles-matching', |
||||
|
{entity: truncate.transform(searchText, true, 6, '...')}) | async }} |
||||
|
</span> |
||||
|
<span> |
||||
|
<a translate (click)="createAssetProfile($event, searchText)">asset-profile.create-new-asset-profile</a> |
||||
|
</span> |
||||
|
</ng-template> |
||||
|
</div> |
||||
|
</mat-option> |
||||
|
</mat-autocomplete> |
||||
|
<mat-error *ngIf="selectAssetProfileFormGroup.get('assetProfile').hasError('required')"> |
||||
|
{{ 'asset-profile.asset-profile-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-hint *ngIf="hint && !disabled">{{ hint | translate }}</mat-hint> |
||||
|
</mat-form-field> |
||||
@ -0,0 +1,21 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2022 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; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,369 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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, |
||||
|
EventEmitter, |
||||
|
forwardRef, |
||||
|
Input, |
||||
|
NgZone, |
||||
|
OnInit, |
||||
|
Output, |
||||
|
ViewChild |
||||
|
} from '@angular/core'; |
||||
|
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; |
||||
|
import { Observable, of } from 'rxjs'; |
||||
|
import { PageLink } from '@shared/models/page/page-link'; |
||||
|
import { Direction } from '@shared/models/page/sort-order'; |
||||
|
import { catchError, debounceTime, distinctUntilChanged, map, share, switchMap, 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 { entityIdEquals } from '@shared/models/id/entity-id'; |
||||
|
import { TruncatePipe } from '@shared//pipe/truncate.pipe'; |
||||
|
import { ENTER } from '@angular/cdk/keycodes'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { MatAutocomplete } from '@angular/material/autocomplete'; |
||||
|
import { emptyPageData } from '@shared/models/page/page-data'; |
||||
|
import { getEntityDetailsPageURL } from '@core/utils'; |
||||
|
import { AssetProfileId } from '@shared/models/id/asset-profile-id'; |
||||
|
import { AssetProfile, AssetProfileInfo } from '@shared/models/asset.models'; |
||||
|
import { AssetProfileService } from '@core/http/asset-profile.service'; |
||||
|
import { AssetProfileDialogComponent, AssetProfileDialogData } from './asset-profile-dialog.component'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-asset-profile-autocomplete', |
||||
|
templateUrl: './asset-profile-autocomplete.component.html', |
||||
|
styleUrls: ['./asset-profile-autocomplete.component.scss'], |
||||
|
providers: [{ |
||||
|
provide: NG_VALUE_ACCESSOR, |
||||
|
useExisting: forwardRef(() => AssetProfileAutocompleteComponent), |
||||
|
multi: true |
||||
|
}] |
||||
|
}) |
||||
|
export class AssetProfileAutocompleteComponent implements ControlValueAccessor, OnInit { |
||||
|
|
||||
|
selectAssetProfileFormGroup: FormGroup; |
||||
|
|
||||
|
modelValue: AssetProfileId | null; |
||||
|
|
||||
|
@Input() |
||||
|
selectDefaultProfile = false; |
||||
|
|
||||
|
@Input() |
||||
|
selectFirstProfile = false; |
||||
|
|
||||
|
@Input() |
||||
|
displayAllOnEmpty = false; |
||||
|
|
||||
|
@Input() |
||||
|
editProfileEnabled = true; |
||||
|
|
||||
|
@Input() |
||||
|
addNewProfile = true; |
||||
|
|
||||
|
@Input() |
||||
|
showDetailsPageLink = false; |
||||
|
|
||||
|
private requiredValue: boolean; |
||||
|
get required(): boolean { |
||||
|
return this.requiredValue; |
||||
|
} |
||||
|
@Input() |
||||
|
set required(value: boolean) { |
||||
|
this.requiredValue = coerceBooleanProperty(value); |
||||
|
} |
||||
|
|
||||
|
@Input() |
||||
|
disabled: boolean; |
||||
|
|
||||
|
@Input() |
||||
|
hint: string; |
||||
|
|
||||
|
@Output() |
||||
|
assetProfileUpdated = new EventEmitter<AssetProfileId>(); |
||||
|
|
||||
|
@Output() |
||||
|
assetProfileChanged = new EventEmitter<AssetProfileInfo>(); |
||||
|
|
||||
|
@ViewChild('assetProfileInput', {static: true}) assetProfileInput: ElementRef; |
||||
|
|
||||
|
@ViewChild('assetProfileAutocomplete', {static: true}) assetProfileAutocomplete: MatAutocomplete; |
||||
|
|
||||
|
filteredAssetProfiles: Observable<Array<AssetProfileInfo>>; |
||||
|
|
||||
|
searchText = ''; |
||||
|
assetProfileURL: string; |
||||
|
|
||||
|
private dirty = false; |
||||
|
|
||||
|
private ignoreClosedPanel = false; |
||||
|
|
||||
|
private allAssetProfile: AssetProfileInfo = { |
||||
|
name: this.translate.instant('asset-profile.all-asset-profiles'), |
||||
|
id: null |
||||
|
}; |
||||
|
|
||||
|
private propagateChange = (v: any) => { }; |
||||
|
|
||||
|
constructor(private store: Store<AppState>, |
||||
|
public translate: TranslateService, |
||||
|
public truncate: TruncatePipe, |
||||
|
private assetProfileService: AssetProfileService, |
||||
|
private fb: FormBuilder, |
||||
|
private zone: NgZone, |
||||
|
private dialog: MatDialog) { |
||||
|
this.selectAssetProfileFormGroup = this.fb.group({ |
||||
|
assetProfile: [null] |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
registerOnChange(fn: any): void { |
||||
|
this.propagateChange = fn; |
||||
|
} |
||||
|
|
||||
|
registerOnTouched(fn: any): void { |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.filteredAssetProfiles = this.selectAssetProfileFormGroup.get('assetProfile').valueChanges |
||||
|
.pipe( |
||||
|
tap((value: AssetProfileInfo | string) => { |
||||
|
let modelValue: AssetProfileInfo | null; |
||||
|
if (typeof value === 'string' || !value) { |
||||
|
modelValue = null; |
||||
|
} else { |
||||
|
modelValue = value; |
||||
|
} |
||||
|
if (!this.displayAllOnEmpty || modelValue) { |
||||
|
this.updateView(modelValue); |
||||
|
} |
||||
|
}), |
||||
|
map(value => { |
||||
|
if (value) { |
||||
|
if (typeof value === 'string') { |
||||
|
return value; |
||||
|
} else { |
||||
|
if (this.displayAllOnEmpty && value === this.allAssetProfile) { |
||||
|
return ''; |
||||
|
} else { |
||||
|
return value.name; |
||||
|
} |
||||
|
} |
||||
|
} else { |
||||
|
return ''; |
||||
|
} |
||||
|
}), |
||||
|
debounceTime(150), |
||||
|
distinctUntilChanged(), |
||||
|
switchMap(name => this.fetchAssetProfiles(name)), |
||||
|
share() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
selectDefaultAssetProfileIfNeeded(): void { |
||||
|
if (this.selectDefaultProfile && !this.modelValue) { |
||||
|
this.assetProfileService.getDefaultAssetProfileInfo().subscribe( |
||||
|
(profile) => { |
||||
|
if (profile) { |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(profile, {emitEvent: false}); |
||||
|
this.updateView(profile); |
||||
|
} else { |
||||
|
this.selectFirstAssetProfileIfNeeded(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} else { |
||||
|
this.selectFirstAssetProfileIfNeeded(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
selectFirstAssetProfileIfNeeded(): void { |
||||
|
if (this.selectFirstProfile && !this.modelValue) { |
||||
|
const pageLink = new PageLink(1, 0, null, { |
||||
|
property: 'createdTime', |
||||
|
direction: Direction.DESC |
||||
|
}); |
||||
|
this.assetProfileService.getAssetProfileInfos(pageLink, {ignoreLoading: true}).subscribe( |
||||
|
(pageData => { |
||||
|
const data = pageData.data; |
||||
|
if (data.length) { |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(data[0], {emitEvent: false}); |
||||
|
this.updateView(data[0]); |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
setDisabledState(isDisabled: boolean): void { |
||||
|
this.disabled = isDisabled; |
||||
|
if (this.disabled) { |
||||
|
this.selectAssetProfileFormGroup.disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.selectAssetProfileFormGroup.enable({emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writeValue(value: AssetProfileId | null): void { |
||||
|
this.searchText = ''; |
||||
|
if (value != null) { |
||||
|
this.assetProfileService.getAssetProfileInfo(value.id).subscribe( |
||||
|
(profile) => { |
||||
|
this.modelValue = new AssetProfileId(profile.id.id); |
||||
|
this.assetProfileURL = getEntityDetailsPageURL(this.modelValue.id, this.modelValue.entityType); |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(profile, {emitEvent: false}); |
||||
|
this.assetProfileChanged.emit(profile); |
||||
|
} |
||||
|
); |
||||
|
} else if (this.displayAllOnEmpty) { |
||||
|
this.modelValue = null; |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(this.allAssetProfile, {emitEvent: false}); |
||||
|
} else { |
||||
|
this.modelValue = null; |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(null, {emitEvent: false}); |
||||
|
this.selectDefaultAssetProfileIfNeeded(); |
||||
|
} |
||||
|
this.dirty = true; |
||||
|
} |
||||
|
|
||||
|
onFocus() { |
||||
|
if (this.dirty) { |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').updateValueAndValidity({onlySelf: true, emitEvent: true}); |
||||
|
this.dirty = false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
onPanelClosed() { |
||||
|
if (this.ignoreClosedPanel) { |
||||
|
this.ignoreClosedPanel = false; |
||||
|
} else { |
||||
|
if (this.displayAllOnEmpty && !this.selectAssetProfileFormGroup.get('assetProfile').value) { |
||||
|
this.zone.run(() => { |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(this.allAssetProfile, {emitEvent: true}); |
||||
|
}, 0); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
updateView(assetProfile: AssetProfileInfo | null) { |
||||
|
const idValue = assetProfile && assetProfile.id ? new AssetProfileId(assetProfile.id.id) : null; |
||||
|
if (!entityIdEquals(this.modelValue, idValue)) { |
||||
|
this.modelValue = idValue; |
||||
|
this.propagateChange(this.modelValue); |
||||
|
this.assetProfileChanged.emit(assetProfile); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
displayAssetProfileFn(profile?: AssetProfileInfo): string | undefined { |
||||
|
return profile ? profile.name : undefined; |
||||
|
} |
||||
|
|
||||
|
fetchAssetProfiles(searchText?: string): Observable<Array<AssetProfileInfo>> { |
||||
|
this.searchText = searchText; |
||||
|
const pageLink = new PageLink(10, 0, searchText, { |
||||
|
property: 'name', |
||||
|
direction: Direction.ASC |
||||
|
}); |
||||
|
return this.assetProfileService.getAssetProfileInfos(pageLink, {ignoreLoading: true}).pipe( |
||||
|
catchError(() => of(emptyPageData<AssetProfileInfo>())), |
||||
|
map(pageData => { |
||||
|
let data = pageData.data; |
||||
|
if (this.displayAllOnEmpty) { |
||||
|
data = [this.allAssetProfile, ...data]; |
||||
|
} |
||||
|
return data; |
||||
|
}) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
clear() { |
||||
|
this.ignoreClosedPanel = true; |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(null, {emitEvent: true}); |
||||
|
setTimeout(() => { |
||||
|
this.assetProfileInput.nativeElement.blur(); |
||||
|
this.assetProfileInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} |
||||
|
|
||||
|
textIsNotEmpty(text: string): boolean { |
||||
|
return (text && text.length > 0); |
||||
|
} |
||||
|
|
||||
|
assetProfileEnter($event: KeyboardEvent) { |
||||
|
if (this.editProfileEnabled && $event.keyCode === ENTER) { |
||||
|
$event.preventDefault(); |
||||
|
if (!this.modelValue) { |
||||
|
this.createAssetProfile($event, this.searchText); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
createAssetProfile($event: Event, profileName: string) { |
||||
|
$event.preventDefault(); |
||||
|
const assetProfile: AssetProfile = { |
||||
|
name: profileName |
||||
|
} as AssetProfile; |
||||
|
if (this.addNewProfile) { |
||||
|
this.openAssetProfileDialog(assetProfile, true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
editAssetProfile($event: Event) { |
||||
|
$event.preventDefault(); |
||||
|
this.assetProfileService.getAssetProfile(this.modelValue.id).subscribe( |
||||
|
(assetProfile) => { |
||||
|
this.openAssetProfileDialog(assetProfile, false); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
openAssetProfileDialog(assetProfile: AssetProfile, isAdd: boolean) { |
||||
|
this.dialog.open<AssetProfileDialogComponent, AssetProfileDialogData, |
||||
|
AssetProfile>(AssetProfileDialogComponent, { |
||||
|
disableClose: true, |
||||
|
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], |
||||
|
data: { |
||||
|
isAdd, |
||||
|
assetProfile |
||||
|
} |
||||
|
}).afterClosed().subscribe( |
||||
|
(savedAssetProfile) => { |
||||
|
if (!savedAssetProfile) { |
||||
|
setTimeout(() => { |
||||
|
this.assetProfileInput.nativeElement.blur(); |
||||
|
this.assetProfileInput.nativeElement.focus(); |
||||
|
}, 0); |
||||
|
} else { |
||||
|
this.assetProfileService.getAssetProfileInfo(savedAssetProfile.id.id).subscribe( |
||||
|
(profile) => { |
||||
|
this.modelValue = new AssetProfileId(profile.id.id); |
||||
|
this.selectAssetProfileFormGroup.get('assetProfile').patchValue(profile, {emitEvent: true}); |
||||
|
if (isAdd) { |
||||
|
this.propagateChange(this.modelValue); |
||||
|
} else { |
||||
|
this.assetProfileUpdated.next(savedAssetProfile.id); |
||||
|
} |
||||
|
this.assetProfileChanged.emit(profile); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2022 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form (ngSubmit)="save()" style="width: 600px;"> |
||||
|
<mat-toolbar color="primary"> |
||||
|
<h2>{{ (isAdd ? 'asset-profile.add' : 'asset-profile.edit' ) | translate }}</h2> |
||||
|
<span fxFlex></span> |
||||
|
<button mat-icon-button |
||||
|
(click)="cancel()" |
||||
|
type="button"> |
||||
|
<mat-icon class="material-icons">close</mat-icon> |
||||
|
</button> |
||||
|
</mat-toolbar> |
||||
|
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async"> |
||||
|
</mat-progress-bar> |
||||
|
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> |
||||
|
<div mat-dialog-content> |
||||
|
<tb-asset-profile |
||||
|
#assetProfileComponent |
||||
|
[standalone]="true" |
||||
|
[entity]="assetProfile" |
||||
|
[isEdit]="true"> |
||||
|
</tb-asset-profile> |
||||
|
</div> |
||||
|
<div mat-dialog-actions fxLayoutAlign="end center"> |
||||
|
<button mat-button color="primary" |
||||
|
type="button" |
||||
|
cdkFocusInitial |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="cancel()"> |
||||
|
{{ 'action.cancel' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
type="submit" |
||||
|
[disabled]="(isLoading$ | async) || assetProfileComponent.entityForm?.invalid || !assetProfileComponent.entityForm?.dirty"> |
||||
|
{{ (isAdd ? 'action.add' : 'action.save') | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,100 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { |
||||
|
AfterViewInit, |
||||
|
Component, |
||||
|
ComponentFactoryResolver, |
||||
|
Inject, |
||||
|
Injector, |
||||
|
SkipSelf, |
||||
|
ViewChild |
||||
|
} from '@angular/core'; |
||||
|
import { ErrorStateMatcher } from '@angular/material/core'; |
||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms'; |
||||
|
import { DialogComponent } from '@shared/components/dialog.component'; |
||||
|
import { Router } from '@angular/router'; |
||||
|
import { AssetProfile } from '@shared/models/asset.models'; |
||||
|
import { AssetProfileComponent } from '@home/components/profile/asset-profile.component'; |
||||
|
import { AssetProfileService } from '@core/http/asset-profile.service'; |
||||
|
|
||||
|
export interface AssetProfileDialogData { |
||||
|
assetProfile: AssetProfile; |
||||
|
isAdd: boolean; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-asset-profile-dialog', |
||||
|
templateUrl: './asset-profile-dialog.component.html', |
||||
|
providers: [{provide: ErrorStateMatcher, useExisting: AssetProfileDialogComponent}], |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class AssetProfileDialogComponent extends |
||||
|
DialogComponent<AssetProfileDialogComponent, AssetProfile> implements ErrorStateMatcher, AfterViewInit { |
||||
|
|
||||
|
isAdd: boolean; |
||||
|
assetProfile: AssetProfile; |
||||
|
|
||||
|
submitted = false; |
||||
|
|
||||
|
@ViewChild('assetProfileComponent', {static: true}) assetProfileComponent: AssetProfileComponent; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected router: Router, |
||||
|
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogData, |
||||
|
public dialogRef: MatDialogRef<AssetProfileDialogComponent, AssetProfile>, |
||||
|
private componentFactoryResolver: ComponentFactoryResolver, |
||||
|
private injector: Injector, |
||||
|
@SkipSelf() private errorStateMatcher: ErrorStateMatcher, |
||||
|
private assetProfileService: AssetProfileService) { |
||||
|
super(store, router, dialogRef); |
||||
|
this.isAdd = this.data.isAdd; |
||||
|
this.assetProfile = this.data.assetProfile; |
||||
|
} |
||||
|
|
||||
|
ngAfterViewInit(): void { |
||||
|
if (this.isAdd) { |
||||
|
setTimeout(() => { |
||||
|
this.assetProfileComponent.entityForm.markAsDirty(); |
||||
|
}, 0); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { |
||||
|
const originalErrorState = this.errorStateMatcher.isErrorState(control, form); |
||||
|
const customErrorState = !!(control && control.invalid && this.submitted); |
||||
|
return originalErrorState || customErrorState; |
||||
|
} |
||||
|
|
||||
|
cancel(): void { |
||||
|
this.dialogRef.close(null); |
||||
|
} |
||||
|
|
||||
|
save(): void { |
||||
|
this.submitted = true; |
||||
|
if (this.assetProfileComponent.entityForm.valid) { |
||||
|
this.assetProfile = {...this.assetProfile, ...this.assetProfileComponent.entityFormValue()}; |
||||
|
this.assetProfileService.saveAssetProfile(this.assetProfile).subscribe( |
||||
|
(assetProfile) => { |
||||
|
this.dialogRef.close(assetProfile); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2022 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, 'open')" |
||||
|
[fxShow]="!isEdit && !isDetailsPage"> |
||||
|
{{'common.open-details-page' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'export')" |
||||
|
[fxShow]="!isEdit"> |
||||
|
{{'asset-profile.export' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'setDefault')" |
||||
|
[fxShow]="!isEdit && !entity?.default"> |
||||
|
{{'asset-profile.set-default' | translate }} |
||||
|
</button> |
||||
|
<button mat-raised-button color="primary" |
||||
|
[disabled]="(isLoading$ | async)" |
||||
|
(click)="onEntityAction($event, 'delete')" |
||||
|
[fxShow]="!hideDelete() && !isEdit"> |
||||
|
{{'asset-profile.delete' | translate }} |
||||
|
</button> |
||||
|
<div fxLayout="row" fxLayout.xs="column"> |
||||
|
<button mat-raised-button |
||||
|
ngxClipboard |
||||
|
(cbOnSuccess)="onAssetProfileIdCopied($event)" |
||||
|
[cbContent]="entity?.id?.id" |
||||
|
[fxShow]="!isEdit"> |
||||
|
<mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> |
||||
|
<span translate>asset-profile.copyId</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div [ngClass]="{'mat-padding': !standalone}" fxLayout="column"> |
||||
|
<form [formGroup]="entityForm"> |
||||
|
<fieldset [disabled]="(isLoading$ | async) || !isEdit" style="min-width: 0;"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>asset-profile.name</mat-label> |
||||
|
<input matInput formControlName="name" required/> |
||||
|
<mat-error *ngIf="entityForm.get('name').hasError('required')"> |
||||
|
{{ 'asset-profile.name-required' | translate }} |
||||
|
</mat-error> |
||||
|
<mat-error *ngIf="entityForm.get('name').hasError('maxlength')"> |
||||
|
{{ 'asset-profile.name-max-length' | translate }} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
<tb-rule-chain-autocomplete |
||||
|
labelText="asset-profile.default-rule-chain" |
||||
|
formControlName="defaultRuleChainId"> |
||||
|
</tb-rule-chain-autocomplete> |
||||
|
<tb-dashboard-autocomplete |
||||
|
placeholder="{{'asset-profile.mobile-dashboard' | translate}}" |
||||
|
formControlName="defaultDashboardId"> |
||||
|
<div tb-hint>{{'asset-profile.mobile-dashboard-hint' | translate}}</div> |
||||
|
</tb-dashboard-autocomplete> |
||||
|
<tb-queue-autocomplete |
||||
|
[queueType]="serviceType" |
||||
|
formControlName="defaultQueueName"> |
||||
|
</tb-queue-autocomplete> |
||||
|
<tb-image-input fxFlex |
||||
|
label="{{'asset-profile.image' | translate}}" |
||||
|
maxSizeByte="524288" |
||||
|
formControlName="image"> |
||||
|
</tb-image-input> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label translate>asset-profile.description</mat-label> |
||||
|
<textarea matInput formControlName="description" rows="2"></textarea> |
||||
|
</mat-form-field> |
||||
|
</fieldset> |
||||
|
</form> |
||||
|
</div> |
||||
@ -0,0 +1,111 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { ChangeDetectorRef, Component, Inject, Input, Optional } from '@angular/core'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
||||
|
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'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
import { RuleChainId } from '@shared/models/id/rule-chain-id'; |
||||
|
import { ServiceType } from '@shared/models/queue.models'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { DashboardId } from '@shared/models/id/dashboard-id'; |
||||
|
import { AssetProfile } from '@shared/models/asset.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-asset-profile', |
||||
|
templateUrl: './asset-profile.component.html', |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class AssetProfileComponent extends EntityComponent<AssetProfile> { |
||||
|
|
||||
|
@Input() |
||||
|
standalone = false; |
||||
|
|
||||
|
entityType = EntityType; |
||||
|
|
||||
|
serviceType = ServiceType.TB_RULE_ENGINE; |
||||
|
|
||||
|
assetProfileId: EntityId; |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
protected translate: TranslateService, |
||||
|
@Optional() @Inject('entity') protected entityValue: AssetProfile, |
||||
|
@Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig<AssetProfile>, |
||||
|
protected fb: FormBuilder, |
||||
|
protected cd: ChangeDetectorRef) { |
||||
|
super(store, fb, entityValue, entitiesTableConfigValue, cd); |
||||
|
} |
||||
|
|
||||
|
hideDelete() { |
||||
|
if (this.entitiesTableConfig) { |
||||
|
return !this.entitiesTableConfig.deleteEnabled(this.entity); |
||||
|
} else { |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
buildForm(entity: AssetProfile): FormGroup { |
||||
|
this.assetProfileId = entity?.id ? entity.id : null; |
||||
|
const form = this.fb.group( |
||||
|
{ |
||||
|
name: [entity ? entity.name : '', [Validators.required, Validators.maxLength(255)]], |
||||
|
image: [entity ? entity.image : null], |
||||
|
defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], |
||||
|
defaultDashboardId: [entity && entity.defaultDashboardId ? entity.defaultDashboardId.id : null, []], |
||||
|
defaultQueueName: [entity ? entity.defaultQueueName : null, []], |
||||
|
description: [entity ? entity.description : '', []], |
||||
|
} |
||||
|
); |
||||
|
return form; |
||||
|
} |
||||
|
|
||||
|
updateForm(entity: AssetProfile) { |
||||
|
this.assetProfileId = entity.id; |
||||
|
this.entityForm.patchValue({name: entity.name}); |
||||
|
this.entityForm.patchValue({image: entity.image}, {emitEvent: false}); |
||||
|
this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); |
||||
|
this.entityForm.patchValue({defaultDashboardId: entity.defaultDashboardId ? entity.defaultDashboardId.id : null}, {emitEvent: false}); |
||||
|
this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); |
||||
|
this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); |
||||
|
} |
||||
|
|
||||
|
prepareFormValue(formValue: any): any { |
||||
|
if (formValue.defaultRuleChainId) { |
||||
|
formValue.defaultRuleChainId = new RuleChainId(formValue.defaultRuleChainId); |
||||
|
} |
||||
|
if (formValue.defaultDashboardId) { |
||||
|
formValue.defaultDashboardId = new DashboardId(formValue.defaultDashboardId); |
||||
|
} |
||||
|
return super.prepareFormValue(formValue); |
||||
|
} |
||||
|
|
||||
|
onAssetProfileIdCopied(event) { |
||||
|
this.store.dispatch(new ActionNotificationShow( |
||||
|
{ |
||||
|
message: this.translate.instant('asset-profile.idCopiedMessage'), |
||||
|
type: 'success', |
||||
|
duration: 750, |
||||
|
verticalPosition: 'bottom', |
||||
|
horizontalPosition: 'right' |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { Routes } from '@angular/router'; |
||||
|
|
||||
|
import { EntitiesTableComponent } from '../../components/entity/entities-table.component'; |
||||
|
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 '@home/pages/home-pages.models'; |
||||
|
import { BreadCrumbConfig } from '@shared/components/breadcrumb'; |
||||
|
import { AssetProfilesTableConfigResolver } from './asset-profiles-table-config.resolver'; |
||||
|
|
||||
|
export const assetProfilesRoutes: Routes = [ |
||||
|
{ |
||||
|
path: 'assetProfiles', |
||||
|
data: { |
||||
|
breadcrumb: { |
||||
|
label: 'asset-profile.asset-profiles', |
||||
|
icon: 'mdi:alpha-a-box' |
||||
|
} |
||||
|
}, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
component: EntitiesTableComponent, |
||||
|
data: { |
||||
|
auth: [Authority.TENANT_ADMIN], |
||||
|
title: 'asset-profile.asset-profiles' |
||||
|
}, |
||||
|
resolve: { |
||||
|
entitiesTableConfig: AssetProfilesTableConfigResolver |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
path: ':entityId', |
||||
|
component: EntityDetailsPageComponent, |
||||
|
canDeactivate: [ConfirmOnExitGuard], |
||||
|
data: { |
||||
|
breadcrumb: { |
||||
|
labelFunction: entityDetailsPageBreadcrumbLabelFunction, |
||||
|
icon: 'mdi:alpha-a-box' |
||||
|
} as BreadCrumbConfig<EntityDetailsPageComponent>, |
||||
|
auth: [Authority.TENANT_ADMIN], |
||||
|
title: 'asset-profile.asset-profiles' |
||||
|
}, |
||||
|
resolve: { |
||||
|
entitiesTableConfig: AssetProfilesTableConfigResolver |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
providers: [ |
||||
|
AssetProfilesTableConfigResolver |
||||
|
] |
||||
|
}) |
||||
|
export class AssetProfileRoutingModule { } |
||||
@ -0,0 +1,27 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2022 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 && !isEdit" #auditLogsTab="matTab" |
||||
|
label="{{ 'audit-log.audit-logs' | translate }}"> |
||||
|
<tb-audit-log-table detailsMode="true" [active]="auditLogsTab.isActive" [auditLogMode]="auditLogModes.ENTITY" [entityId]="entity.id"></tb-audit-log-table> |
||||
|
</mat-tab> |
||||
|
<mat-tab *ngIf="entity && authUser.authority === authorities.TENANT_ADMIN && !isEdit" |
||||
|
label="{{ 'version-control.version-control' | translate }}" #versionControlTab="matTab"> |
||||
|
<tb-version-control detailsMode="true" singleEntityMode="true" |
||||
|
(versionRestored)="entitiesTableConfig.updateData()" |
||||
|
[active]="versionControlTab.isActive" [entityId]="entity.id" [entityName]="entity.name" [externalEntityId]="entity.externalId || entity.id"></tb-version-control> |
||||
|
</mat-tab> |
||||
@ -0,0 +1,38 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { AssetProfile } from '@shared/models/asset.models'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-asset-profile-tabs', |
||||
|
templateUrl: './asset-profile-tabs.component.html', |
||||
|
styleUrls: [] |
||||
|
}) |
||||
|
export class AssetProfileTabsComponent extends EntityTabsComponent<AssetProfile> { |
||||
|
|
||||
|
constructor(protected store: Store<AppState>) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
super.ngOnInit(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,35 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { AssetProfileTabsComponent } from './asset-profile-tabs.component'; |
||||
|
import { AssetProfileRoutingModule } from './asset-profile-routing.module'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ |
||||
|
AssetProfileTabsComponent |
||||
|
], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
HomeComponentsModule, |
||||
|
AssetProfileRoutingModule |
||||
|
] |
||||
|
}) |
||||
|
export class AssetProfileModule { } |
||||
@ -0,0 +1,189 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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, Router } from '@angular/router'; |
||||
|
import { |
||||
|
checkBoxCell, |
||||
|
DateEntityTableColumn, |
||||
|
EntityTableColumn, |
||||
|
EntityTableConfig, |
||||
|
HeaderActionDescriptor |
||||
|
} 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 { DialogService } from '@core/services/dialog.service'; |
||||
|
import { MatDialog } from '@angular/material/dialog'; |
||||
|
import { ImportExportService } from '@home/components/import-export/import-export.service'; |
||||
|
import { HomeDialogsService } from '@home/dialogs/home-dialogs.service'; |
||||
|
import { AssetProfile } from '@shared/models/asset.models'; |
||||
|
import { AssetProfileService } from '@core/http/asset-profile.service'; |
||||
|
import { AssetProfileComponent } from '@home/components/profile/asset-profile.component'; |
||||
|
import { AssetProfileTabsComponent } from './asset-profile-tabs.component'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class AssetProfilesTableConfigResolver implements Resolve<EntityTableConfig<AssetProfile>> { |
||||
|
|
||||
|
private readonly config: EntityTableConfig<AssetProfile> = new EntityTableConfig<AssetProfile>(); |
||||
|
|
||||
|
constructor(private assetProfileService: AssetProfileService, |
||||
|
private importExport: ImportExportService, |
||||
|
private homeDialogs: HomeDialogsService, |
||||
|
private translate: TranslateService, |
||||
|
private datePipe: DatePipe, |
||||
|
private dialogService: DialogService, |
||||
|
private router: Router, |
||||
|
private dialog: MatDialog) { |
||||
|
|
||||
|
this.config.entityType = EntityType.ASSET_PROFILE; |
||||
|
this.config.entityComponent = AssetProfileComponent; |
||||
|
this.config.entityTabsComponent = AssetProfileTabsComponent; |
||||
|
this.config.entityTranslations = entityTypeTranslations.get(EntityType.ASSET_PROFILE); |
||||
|
this.config.entityResources = entityTypeResources.get(EntityType.ASSET_PROFILE); |
||||
|
|
||||
|
this.config.hideDetailsTabsOnEdit = false; |
||||
|
|
||||
|
this.config.columns.push( |
||||
|
new DateEntityTableColumn<AssetProfile>('createdTime', 'common.created-time', this.datePipe, '150px'), |
||||
|
new EntityTableColumn<AssetProfile>('name', 'asset-profile.name', '50%'), |
||||
|
new EntityTableColumn<AssetProfile>('description', 'asset-profile.description', '50%'), |
||||
|
new EntityTableColumn<AssetProfile>('isDefault', 'asset-profile.default', '60px', |
||||
|
entity => { |
||||
|
return checkBoxCell(entity.default); |
||||
|
}) |
||||
|
); |
||||
|
|
||||
|
this.config.cellActionDescriptors.push( |
||||
|
{ |
||||
|
name: this.translate.instant('asset-profile.export'), |
||||
|
icon: 'file_download', |
||||
|
isEnabled: () => true, |
||||
|
onAction: ($event, entity) => this.exportAssetProfile($event, entity) |
||||
|
}, |
||||
|
{ |
||||
|
name: this.translate.instant('asset-profile.set-default'), |
||||
|
icon: 'flag', |
||||
|
isEnabled: (assetProfile) => !assetProfile.default, |
||||
|
onAction: ($event, entity) => this.setDefaultAssetProfile($event, entity) |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
this.config.deleteEntityTitle = assetProfile => this.translate.instant('asset-profile.delete-asset-profile-title', |
||||
|
{ assetProfileName: assetProfile.name }); |
||||
|
this.config.deleteEntityContent = () => this.translate.instant('asset-profile.delete-asset-profile-text'); |
||||
|
this.config.deleteEntitiesTitle = count => this.translate.instant('asset-profile.delete-asset-profiles-title', {count}); |
||||
|
this.config.deleteEntitiesContent = () => this.translate.instant('asset-profile.delete-asset-profiles-text'); |
||||
|
|
||||
|
this.config.entitiesFetchFunction = pageLink => this.assetProfileService.getAssetProfiles(pageLink); |
||||
|
this.config.loadEntity = id => this.assetProfileService.getAssetProfile(id.id); |
||||
|
this.config.saveEntity = assetProfile => this.assetProfileService.saveAssetProfile(assetProfile); |
||||
|
this.config.deleteEntity = id => this.assetProfileService.deleteAssetProfile(id.id); |
||||
|
this.config.onEntityAction = action => this.onAssetProfileAction(action); |
||||
|
this.config.deleteEnabled = (assetProfile) => assetProfile && !assetProfile.default; |
||||
|
this.config.entitySelectionEnabled = (assetProfile) => assetProfile && !assetProfile.default; |
||||
|
this.config.addActionDescriptors = this.configureAddActions(); |
||||
|
} |
||||
|
|
||||
|
resolve(): EntityTableConfig<AssetProfile> { |
||||
|
this.config.tableTitle = this.translate.instant('asset-profile.asset-profiles'); |
||||
|
|
||||
|
return this.config; |
||||
|
} |
||||
|
|
||||
|
configureAddActions(): Array<HeaderActionDescriptor> { |
||||
|
const actions: Array<HeaderActionDescriptor> = []; |
||||
|
actions.push( |
||||
|
{ |
||||
|
name: this.translate.instant('asset-profile.create-asset-profile'), |
||||
|
icon: 'insert_drive_file', |
||||
|
isEnabled: () => true, |
||||
|
onAction: ($event) => this.config.getTable().addEntity($event) |
||||
|
}, |
||||
|
{ |
||||
|
name: this.translate.instant('asset-profile.import'), |
||||
|
icon: 'file_upload', |
||||
|
isEnabled: () => true, |
||||
|
onAction: ($event) => this.importAssetProfile($event) |
||||
|
} |
||||
|
); |
||||
|
return actions; |
||||
|
} |
||||
|
|
||||
|
setDefaultAssetProfile($event: Event, assetProfile: AssetProfile) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
this.dialogService.confirm( |
||||
|
this.translate.instant('asset-profile.set-default-asset-profile-title', {assetProfileName: assetProfile.name}), |
||||
|
this.translate.instant('asset-profile.set-default-asset-profile-text'), |
||||
|
this.translate.instant('action.no'), |
||||
|
this.translate.instant('action.yes'), |
||||
|
true |
||||
|
).subscribe((res) => { |
||||
|
if (res) { |
||||
|
this.assetProfileService.setDefaultAssetProfile(assetProfile.id.id).subscribe( |
||||
|
() => { |
||||
|
this.config.updateData(); |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
private openAssetProfile($event: Event, assetProfile: AssetProfile) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
const url = this.router.createUrlTree(['profiles', 'assetProfiles', assetProfile.id.id]); |
||||
|
this.router.navigateByUrl(url); |
||||
|
} |
||||
|
|
||||
|
importAssetProfile($event: Event) { |
||||
|
this.importExport.importAssetProfile().subscribe( |
||||
|
(assetProfile) => { |
||||
|
if (assetProfile) { |
||||
|
this.config.updateData(); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
exportAssetProfile($event: Event, assetProfile: AssetProfile) { |
||||
|
if ($event) { |
||||
|
$event.stopPropagation(); |
||||
|
} |
||||
|
this.importExport.exportAssetProfile(assetProfile.id.id); |
||||
|
} |
||||
|
|
||||
|
onAssetProfileAction(action: EntityAction<AssetProfile>): boolean { |
||||
|
switch (action.action) { |
||||
|
case 'open': |
||||
|
this.openAssetProfile(action.event, action.entity); |
||||
|
return true; |
||||
|
case 'setDefault': |
||||
|
this.setDefaultAssetProfile(action.event, action.entity); |
||||
|
return true; |
||||
|
case 'export': |
||||
|
this.exportAssetProfile(action.event, action.entity); |
||||
|
return true; |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { RouterModule, Routes } from '@angular/router'; |
||||
|
import { Authority } from '@shared/models/authority.enum'; |
||||
|
import { NgModule } from '@angular/core'; |
||||
|
import { deviceProfilesRoutes } from '@home/pages/device-profile/device-profile-routing.module'; |
||||
|
import { assetProfilesRoutes } from '@home/pages/asset-profile/asset-profile-routing.module'; |
||||
|
|
||||
|
const routes: Routes = [ |
||||
|
{ |
||||
|
path: 'profiles', |
||||
|
data: { |
||||
|
auth: [Authority.TENANT_ADMIN], |
||||
|
breadcrumb: { |
||||
|
label: 'profiles.profiles', |
||||
|
icon: 'badge' |
||||
|
} |
||||
|
}, |
||||
|
children: [ |
||||
|
{ |
||||
|
path: '', |
||||
|
data: { |
||||
|
auth: [Authority.TENANT_ADMIN], |
||||
|
redirectTo: '/profiles/deviceProfiles' |
||||
|
} |
||||
|
}, |
||||
|
...deviceProfilesRoutes, |
||||
|
...assetProfilesRoutes |
||||
|
] |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
@NgModule({ |
||||
|
imports: [RouterModule.forChild(routes)], |
||||
|
exports: [RouterModule] |
||||
|
}) |
||||
|
export class ProfilesRoutingModule { } |
||||
@ -0,0 +1,30 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { ProfilesRoutingModule } from './profiles-routing.module'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
SharedModule, |
||||
|
ProfilesRoutingModule |
||||
|
] |
||||
|
}) |
||||
|
export class ProfilesModule { } |
||||
@ -0,0 +1,26 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2022 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 { EntityId } from './entity-id'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
|
||||
|
export class AssetProfileId implements EntityId { |
||||
|
entityType = EntityType.ASSET_PROFILE; |
||||
|
id: string; |
||||
|
constructor(id: string) { |
||||
|
this.id = id; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue