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