31 changed files with 164 additions and 394 deletions
@ -0,0 +1,63 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2024 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, Input } from '@angular/core'; |
||||
|
import { BaseData } from '@shared/models/base-data'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
import { baseDetailsPageByEntityType, EntityType } from '@app/shared/public-api'; |
||||
|
|
||||
|
const entityTypeEntitiesPropertyKeyMap = new Map<EntityType, string>([ |
||||
|
[EntityType.DOMAIN, 'oauth2ClientInfos'], |
||||
|
[EntityType.MOBILE_APP, 'oauth2ClientInfos'] |
||||
|
]); |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-entity-chips', |
||||
|
templateUrl: './entity-chips.component.html', |
||||
|
styleUrls: ['./entity-chips.component.scss'] |
||||
|
}) |
||||
|
export class EntityChipsComponent { |
||||
|
|
||||
|
@Input() |
||||
|
set entity(value: BaseData<EntityId>) { |
||||
|
this.entityValue = value; |
||||
|
this.update(); |
||||
|
} |
||||
|
|
||||
|
get entity(): BaseData<EntityId> { |
||||
|
return this.entityValue; |
||||
|
} |
||||
|
|
||||
|
entityDetailsPrefixUrl: string; |
||||
|
|
||||
|
subEntities: Array<BaseData<EntityId>>; |
||||
|
|
||||
|
private entityValue?: BaseData<EntityId>; |
||||
|
|
||||
|
private subEntitiesKey: string; |
||||
|
|
||||
|
update(): void { |
||||
|
if (this.entity && this.entity.id) { |
||||
|
const entityType = this.entity.id.entityType as EntityType; |
||||
|
this.subEntitiesKey = entityTypeEntitiesPropertyKeyMap.get(entityType); |
||||
|
this.subEntities = this.entity?.[this.subEntitiesKey]; |
||||
|
if (this.subEntities.length) { |
||||
|
this.entityDetailsPrefixUrl = baseDetailsPageByEntityType.get(this.subEntities[0].id.entityType as EntityType); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,40 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 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, Input } from '@angular/core'; |
|
||||
import { HasOauth2Clients } from '@shared/models/oauth2.models'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-client-chips', |
|
||||
templateUrl: './client-chips.component.html', |
|
||||
styleUrls: ['./client-chips.component.scss'] |
|
||||
}) |
|
||||
export class ClientChipsComponent { |
|
||||
|
|
||||
clientsEntityValue?: HasOauth2Clients; |
|
||||
|
|
||||
clientPrefixUrl = '/security-settings/oauth2/clients/details/'; |
|
||||
|
|
||||
@Input() |
|
||||
set clientsEntity(value: HasOauth2Clients) { |
|
||||
this.clientsEntityValue = value; |
|
||||
} |
|
||||
|
|
||||
get clientsEntity(): HasOauth2Clients { |
|
||||
return this.clientsEntityValue; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,53 +0,0 @@ |
|||||
<!-- |
|
||||
|
|
||||
Copyright © 2016-2024 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]="entityListFormGroup" class="mat-block" [subscriptSizing]="subscriptSizing"> |
|
||||
<mat-label translate>admin.oauth2.clients</mat-label> |
|
||||
<mat-chip-grid #chipList formControlName="entities"> |
|
||||
<mat-chip-row |
|
||||
*ngFor="let entity of entities" |
|
||||
[removable]="!disabled" |
|
||||
(removed)="remove(entity)"> |
|
||||
{{entity.title}} |
|
||||
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon> |
|
||||
</mat-chip-row> |
|
||||
<input matInput type="text" placeholder="Add client" |
|
||||
#entityInput |
|
||||
(focusin)="onFocus()" |
|
||||
formControlName="entity" |
|
||||
matAutocompleteOrigin |
|
||||
#origin="matAutocompleteOrigin" |
|
||||
[matAutocompleteConnectedTo]="origin" |
|
||||
[matAutocomplete]="entityAutocomplete" |
|
||||
[matChipInputFor]="chipList"> |
|
||||
</mat-chip-grid> |
|
||||
<mat-autocomplete #entityAutocomplete="matAutocomplete" |
|
||||
class="tb-autocomplete" |
|
||||
[displayWith]="displayEntityFn"> |
|
||||
<mat-option *ngFor="let entity of filteredEntities | async" [value]="entity"> |
|
||||
<span [innerHTML]="entity.title | highlight:searchText"></span> |
|
||||
</mat-option> |
|
||||
<mat-option *ngIf="!(filteredEntities | async)?.length" [value]="null"> |
|
||||
<span> |
|
||||
{{ translate.get('entity.no-entities-matching', {entity: searchText}) | async }} |
|
||||
</span> |
|
||||
</mat-option> |
|
||||
</mat-autocomplete> |
|
||||
<div matSuffix> |
|
||||
<ng-content select="[matSuffix]"></ng-content> |
|
||||
</div> |
|
||||
</mat-form-field> |
|
||||
@ -1,182 +0,0 @@ |
|||||
///
|
|
||||
/// Copyright © 2016-2024 The Thingsboard Authors
|
|
||||
///
|
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||
/// you may not use this file except in compliance with the License.
|
|
||||
/// You may obtain a copy of the License at
|
|
||||
///
|
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
///
|
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||
/// See the License for the specific language governing permissions and
|
|
||||
/// limitations under the License.
|
|
||||
///
|
|
||||
|
|
||||
import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; |
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; |
|
||||
import { Observable } from 'rxjs'; |
|
||||
import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; |
|
||||
import { Store } from '@ngrx/store'; |
|
||||
import { AppState } from '@app/core/core.state'; |
|
||||
import { TranslateService } from '@ngx-translate/core'; |
|
||||
import { BaseData } from '@shared/models/base-data'; |
|
||||
import { EntityId } from '@shared/models/id/entity-id'; |
|
||||
import { MatAutocomplete } from '@angular/material/autocomplete'; |
|
||||
import { MatChipGrid } from '@angular/material/chips'; |
|
||||
import { SubscriptSizing } from '@angular/material/form-field'; |
|
||||
import { OAuth2Service } from '@core/http/oauth2.service'; |
|
||||
import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; |
|
||||
import { PageLink } from '@app/shared/models/page/page-link'; |
|
||||
|
|
||||
@Component({ |
|
||||
selector: 'tb-client-list', |
|
||||
templateUrl: './client-list.component.html', |
|
||||
styleUrls: [], |
|
||||
providers: [ |
|
||||
{ |
|
||||
provide: NG_VALUE_ACCESSOR, |
|
||||
useExisting: forwardRef(() => ClientListComponent), |
|
||||
multi: true |
|
||||
} |
|
||||
] |
|
||||
}) |
|
||||
export class ClientListComponent implements ControlValueAccessor, OnInit { |
|
||||
|
|
||||
entityListFormGroup: UntypedFormGroup; |
|
||||
|
|
||||
modelValue: Array<OAuth2ClientInfo> | null; |
|
||||
|
|
||||
@Input() |
|
||||
disabled: boolean; |
|
||||
|
|
||||
@Input() |
|
||||
subscriptSizing: SubscriptSizing = 'fixed'; |
|
||||
|
|
||||
@ViewChild('entityInput') entityInput: ElementRef<HTMLInputElement>; |
|
||||
@ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete; |
|
||||
@ViewChild('chipList', {static: true}) chipList: MatChipGrid; |
|
||||
|
|
||||
entities: Array<BaseData<EntityId>> = []; |
|
||||
filteredEntities: Observable<Array<BaseData<EntityId>>>; |
|
||||
|
|
||||
searchText = ''; |
|
||||
|
|
||||
private dirty = false; |
|
||||
|
|
||||
private propagateChange = (v: any) => { |
|
||||
}; |
|
||||
|
|
||||
constructor(private store: Store<AppState>, |
|
||||
public translate: TranslateService, |
|
||||
private oauth2Service: OAuth2Service, |
|
||||
private fb: UntypedFormBuilder) { |
|
||||
this.entityListFormGroup = this.fb.group({ |
|
||||
entities: [this.entities], |
|
||||
entity: [null] |
|
||||
}); |
|
||||
} |
|
||||
|
|
||||
registerOnChange(fn: any): void { |
|
||||
this.propagateChange = fn; |
|
||||
} |
|
||||
|
|
||||
registerOnTouched(fn: any): void { |
|
||||
} |
|
||||
|
|
||||
ngOnInit() { |
|
||||
this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges |
|
||||
.pipe( |
|
||||
tap((value) => { |
|
||||
if (value && typeof value !== 'string') { |
|
||||
this.add(value); |
|
||||
} else if (value === null) { |
|
||||
this.clear(this.entityInput.nativeElement.value); |
|
||||
} |
|
||||
}), |
|
||||
filter((value) => typeof value === 'string'), |
|
||||
map((value) => value ? (typeof value === 'string' ? value : value.title) : ''), |
|
||||
mergeMap(name => this.fetchEntities(name)), |
|
||||
share() |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
setDisabledState(isDisabled: boolean): void { |
|
||||
this.disabled = isDisabled; |
|
||||
if (isDisabled) { |
|
||||
this.entityListFormGroup.disable({emitEvent: false}); |
|
||||
} else { |
|
||||
this.entityListFormGroup.enable({emitEvent: false}); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
writeValue(value: Array<OAuth2ClientInfo> | null): void { |
|
||||
this.searchText = ''; |
|
||||
if (value != null && value.length > 0) { |
|
||||
this.modelValue = [...value]; |
|
||||
this.entities = [...value]; |
|
||||
} else { |
|
||||
this.entities = []; |
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities); |
|
||||
this.modelValue = null; |
|
||||
} |
|
||||
this.dirty = true; |
|
||||
} |
|
||||
|
|
||||
add(entity: OAuth2ClientInfo): void { |
|
||||
if (!this.modelValue || !this.modelValue.find(client => client.id.id === entity.id.id)) { |
|
||||
if (!this.modelValue) { |
|
||||
this.modelValue = []; |
|
||||
} |
|
||||
this.modelValue.push(entity); |
|
||||
this.entities.push(entity); |
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities); |
|
||||
} |
|
||||
this.propagateChange(this.modelValue); |
|
||||
this.clear(); |
|
||||
} |
|
||||
|
|
||||
remove(entity: OAuth2ClientInfo) { |
|
||||
let index = this.entities.indexOf(entity); |
|
||||
if (index >= 0) { |
|
||||
this.entities.splice(index, 1); |
|
||||
this.entityListFormGroup.get('entities').setValue(this.entities); |
|
||||
index = this.modelValue.indexOf(entity); |
|
||||
this.modelValue.splice(index, 1); |
|
||||
if (!this.modelValue.length) { |
|
||||
this.modelValue = null; |
|
||||
} |
|
||||
this.propagateChange(this.modelValue); |
|
||||
this.clear(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
displayEntityFn(entity?: OAuth2ClientInfo): string | undefined { |
|
||||
return entity ? entity.title : undefined; |
|
||||
} |
|
||||
|
|
||||
fetchEntities(searchText?: string): Observable<Array<OAuth2ClientInfo>> { |
|
||||
this.searchText = searchText; |
|
||||
|
|
||||
return this.oauth2Service.findTenantOAuth2ClientInfos(new PageLink(100, 0, searchText), {ignoreLoading: true}).pipe( |
|
||||
map((data) => data ? data.data : [])); |
|
||||
} |
|
||||
|
|
||||
onFocus() { |
|
||||
if (this.dirty) { |
|
||||
this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true}); |
|
||||
this.dirty = false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
clear(value: string = '') { |
|
||||
this.entityInput.nativeElement.value = value; |
|
||||
this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); |
|
||||
setTimeout(() => { |
|
||||
this.entityInput.nativeElement.blur(); |
|
||||
this.entityInput.nativeElement.focus(); |
|
||||
}, 0); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
Loading…
Reference in new issue