Browse Source

UI: oauth - switched to entity-list for clients, introduce entity-chips for tables, fixes improvements

pull/11231/head
rusikv 2 years ago
parent
commit
3c02537454
  1. 4
      ui-ngx/src/app/core/http/domain.service.ts
  2. 12
      ui-ngx/src/app/core/http/entity.service.ts
  3. 4
      ui-ngx/src/app/core/http/oauth2.service.ts
  4. 8
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.html
  5. 4
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  6. 6
      ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html
  7. 2
      ui-ngx/src/app/modules/home/components/entity/entity-chips.component.scss
  8. 63
      ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts
  9. 2
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  10. 7
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  11. 10
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  12. 40
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts
  13. 2
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html
  14. 53
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html
  15. 182
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts
  16. 6
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.html
  17. 4
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.html
  18. 39
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts
  19. 5
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/clients-table-config.resolver.ts
  20. 16
      ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts
  21. 5
      ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.html
  22. 8
      ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html
  23. 17
      ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts
  24. 14
      ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts
  25. 5
      ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html
  26. 6
      ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html
  27. 20
      ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts
  28. 2
      ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts
  29. 4
      ui-ngx/src/app/shared/models/entity-type.models.ts
  30. 7
      ui-ngx/src/app/shared/models/oauth2.models.ts
  31. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

4
ui-ngx/src/app/core/http/domain.service.ts

@ -37,8 +37,8 @@ export class DomainService {
domain, defaultHttpOptionsFromConfig(config)); domain, defaultHttpOptionsFromConfig(config));
} }
public updateOauth2Clients(id: string, oauth2ClientRegistrationIds: Array<string>, config?: RequestConfig): Observable<void> { public updateOauth2Clients(id: string, oauth2ClientIds: Array<string>, config?: RequestConfig): Observable<void> {
return this.http.put<void>(`/api/domain/${id}/oauth2Clients`, oauth2ClientRegistrationIds, defaultHttpOptionsFromConfig(config)); return this.http.put<void>(`/api/domain/${id}/oauth2Clients`, oauth2ClientIds, defaultHttpOptionsFromConfig(config));
} }
public getTenantDomainInfos(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DomainInfo>> { public getTenantDomainInfos(pageLink: PageLink, config?: RequestConfig): Observable<PageData<DomainInfo>> {

12
ui-ngx/src/app/core/http/entity.service.ts

@ -96,6 +96,7 @@ import { NotificationType } from '@shared/models/notification.models';
import { UserId } from '@shared/models/id/user-id'; import { UserId } from '@shared/models/id/user-id';
import { AlarmService } from '@core/http/alarm.service'; import { AlarmService } from '@core/http/alarm.service';
import { ResourceService } from '@core/http/resource.service'; import { ResourceService } from '@core/http/resource.service';
import { OAuth2Service } from '@core/http/oauth2.service';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -125,7 +126,8 @@ export class EntityService {
private queueService: QueueService, private queueService: QueueService,
private notificationService: NotificationService, private notificationService: NotificationService,
private alarmService: AlarmService, private alarmService: AlarmService,
private resourceService: ResourceService private resourceService: ResourceService,
private oauth2Service: OAuth2Service
) { } ) { }
private getEntityObservable(entityType: EntityType, entityId: string, private getEntityObservable(entityType: EntityType, entityId: string,
@ -272,6 +274,9 @@ export class EntityService {
case EntityType.QUEUE_STATS: case EntityType.QUEUE_STATS:
observable = this.queueService.getQueueStatisticsByIds(entityIds, config); observable = this.queueService.getQueueStatisticsByIds(entityIds, config);
break; break;
case EntityType.OAUTH2_CLIENT:
observable = this.oauth2Service.findTenantOAuth2ClientInfosByIds(entityIds, config);
break;
} }
return observable; return observable;
} }
@ -452,6 +457,11 @@ export class EntityService {
case EntityType.QUEUE_STATS: case EntityType.QUEUE_STATS:
pageLink.sortOrder.property = 'createdTime'; pageLink.sortOrder.property = 'createdTime';
entitiesObservable = this.queueService.getQueueStatistics(pageLink, config); entitiesObservable = this.queueService.getQueueStatistics(pageLink, config);
break;
case EntityType.OAUTH2_CLIENT:
pageLink.sortOrder.property = 'createdTime';
entitiesObservable = this.oauth2Service.findTenantOAuth2ClientInfos(pageLink, config);
break;
} }
return entitiesObservable; return entitiesObservable;
} }

4
ui-ngx/src/app/core/http/oauth2.service.ts

@ -44,6 +44,10 @@ export class OAuth2Service {
return this.http.get<PageData<OAuth2ClientInfo>>(`/api/oauth2/client/infos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); return this.http.get<PageData<OAuth2ClientInfo>>(`/api/oauth2/client/infos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config));
} }
public findTenantOAuth2ClientInfosByIds(clientIds: Array<string>, config?: RequestConfig): Observable<Array<OAuth2ClientInfo>> {
return this.http.get<Array<OAuth2ClientInfo>>(`/api/oauth2/client/infos?clientIds=${clientIds.join(',')}`, defaultHttpOptionsFromConfig(config))
}
public getOAuth2ClientById(id: string, config?: RequestConfig): Observable<OAuth2Client> { public getOAuth2ClientById(id: string, config?: RequestConfig): Observable<OAuth2Client> {
return this.http.get<OAuth2Client>(`/api/oauth2/client/${id}`, defaultHttpOptionsFromConfig(config)); return this.http.get<OAuth2Client>(`/api/oauth2/client/${id}`, defaultHttpOptionsFromConfig(config));
} }

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

@ -38,9 +38,7 @@
<mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()"> <mat-toolbar class="mat-mdc-table-toolbar" [fxShow]="!textSearchMode && dataSource.selection.isEmpty()">
<div class="mat-toolbar-tools"> <div class="mat-toolbar-tools">
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container"> <div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container">
<span *ngIf="!entitiesTableConfig.hideTableTitle && entitiesTableConfig.tableTitle" class="tb-entity-table-title"> <span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
{{ entitiesTableConfig.tableTitle }}
</span>
<tb-anchor #entityTableHeader></tb-anchor> <tb-anchor #entityTableHeader></tb-anchor>
<tb-timewindow *ngIf="entitiesTableConfig.useTimePageLink" [(ngModel)]="timewindow" <tb-timewindow *ngIf="entitiesTableConfig.useTimePageLink" [(ngModel)]="timewindow"
(ngModelChange)="onTimewindowChange()" (ngModelChange)="onTimewindowChange()"
@ -173,8 +171,8 @@
[routerLink]="detailsPageURL" [innerHTML]="cellContent(entity, column, row)" [routerLink]="detailsPageURL" [innerHTML]="cellContent(entity, column, row)"
(click)="$event.stopPropagation();"></a> (click)="$event.stopPropagation();"></a>
</ng-template> </ng-template>
<ng-template [ngSwitchCase]="'clients'"> <ng-template [ngSwitchCase]="'entityChips'">
<tb-client-chips [clientsEntity]="entity"></tb-client-chips> <tb-entity-chips [entity]="entity"></tb-entity-chips>
</ng-template> </ng-template>
<ng-template #defaultContent ngSwitchDefault> <ng-template #defaultContent ngSwitchDefault>
<span [innerHTML]="cellContent(entity, column, row)"></span> <span [innerHTML]="cellContent(entity, column, row)"></span>

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

@ -46,8 +46,8 @@ import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router';
import { import {
CellActionDescriptor, CellActionDescriptor,
CellActionDescriptorType, CellActionDescriptorType,
ClientChipsEntityTableColumn,
EntityActionTableColumn, EntityActionTableColumn,
EntityChipsEntityTableColumn,
EntityColumn, EntityColumn,
EntityLinkTableColumn, EntityLinkTableColumn,
EntityTableColumn, EntityTableColumn,
@ -616,7 +616,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
columnsUpdated(resetData: boolean = false) { columnsUpdated(resetData: boolean = false) {
this.entityColumns = this.entitiesTableConfig.columns.filter( this.entityColumns = this.entitiesTableConfig.columns.filter(
(column) => column instanceof EntityTableColumn || column instanceof EntityLinkTableColumn || (column) => column instanceof EntityTableColumn || column instanceof EntityLinkTableColumn ||
column instanceof ClientChipsEntityTableColumn) column instanceof EntityChipsEntityTableColumn)
.map(column => column as EntityTableColumn<BaseData<HasId>>); .map(column => column as EntityTableColumn<BaseData<HasId>>);
this.actionColumns = this.entitiesTableConfig.columns.filter( this.actionColumns = this.entitiesTableConfig.columns.filter(
(column) => column instanceof EntityActionTableColumn) (column) => column instanceof EntityActionTableColumn)

6
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html → ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html

@ -16,7 +16,7 @@
--> -->
<a *ngFor="let client of clientsEntity?.oauth2ClientInfos" class="tb-client-chip" <a *ngFor="let subEntity of subEntities" class="tb-entity-chip"
routerLink="{{clientPrefixUrl}}/{{client.id.id}}" (click)="$event.stopPropagation();"> routerLink="{{entityDetailsPrefixUrl}}/{{subEntity.id.id}}" (click)="$event.stopPropagation();">
{{ client.title }} {{ subEntity.name }}
</a> </a>

2
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss → ui-ngx/src/app/modules/home/components/entity/entity-chips.component.scss

@ -19,7 +19,7 @@
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
margin: 4px 0; margin: 4px 0;
a.tb-client-chip { a.tb-entity-chip {
padding: 4px 8px; padding: 4px 8px;
border-radius: 12px; border-radius: 12px;
line-height: 16px; line-height: 16px;

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

@ -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);
}
}
}
}

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

@ -228,7 +228,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
} }
hideDetailsTabs(): boolean { hideDetailsTabs(): boolean {
return this.entitiesTableConfig.hideDetailsTabs || this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; return this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit;
} }
reloadEntity(): Observable<BaseData<HasId>> { reloadEntity(): Observable<BaseData<HasId>> {

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

@ -171,7 +171,7 @@ import {
import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module';
import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module';
import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component';
import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-chips.component'; import { EntityChipsComponent } from '@home/components/entity/entity-chips.component';
@NgModule({ @NgModule({
declarations: declarations:
@ -310,7 +310,7 @@ import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-ch
RateLimitsTextComponent, RateLimitsTextComponent,
RateLimitsDetailsDialogComponent, RateLimitsDetailsDialogComponent,
SendNotificationButtonComponent, SendNotificationButtonComponent,
ClientChipsComponent EntityChipsComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -441,7 +441,8 @@ import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-ch
RateLimitsComponent, RateLimitsComponent,
RateLimitsTextComponent, RateLimitsTextComponent,
RateLimitsDetailsDialogComponent, RateLimitsDetailsDialogComponent,
SendNotificationButtonComponent SendNotificationButtonComponent,
EntityChipsComponent
], ],
providers: [ providers: [
WidgetComponentService, WidgetComponentService,

10
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -78,7 +78,7 @@ export interface HeaderActionDescriptor {
onAction: ($event: MouseEvent) => void; onAction: ($event: MouseEvent) => void;
} }
export type EntityTableColumnType = 'content' | 'action' | 'link' | 'clients'; export type EntityTableColumnType = 'content' | 'action' | 'link' | 'entityChips';
export class BaseEntityTableColumn<T extends BaseData<HasId>> { export class BaseEntityTableColumn<T extends BaseData<HasId>> {
constructor(public type: EntityTableColumnType, constructor(public type: EntityTableColumnType,
@ -141,15 +141,15 @@ export class DateEntityTableColumn<T extends BaseData<HasId>> extends EntityTabl
} }
} }
export class ClientChipsEntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> { export class EntityChipsEntityTableColumn<T extends BaseData<HasId>> extends BaseEntityTableColumn<T> {
constructor(public key: string, constructor(public key: string,
public title: string, public title: string,
public width: string = '0px') { public width: string = '0px') {
super('clients', key, title, width, false); super('entityChips', key, title, width, false);
} }
} }
export type EntityColumn<T extends BaseData<HasId>> = EntityTableColumn<T> | EntityActionTableColumn<T> | EntityLinkTableColumn<T> | ClientChipsEntityTableColumn<T>; export type EntityColumn<T extends BaseData<HasId>> = EntityTableColumn<T> | EntityActionTableColumn<T> | EntityLinkTableColumn<T> | EntityChipsEntityTableColumn<T>;
export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = PageLink, L extends BaseData<HasId> = T> { export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = PageLink, L extends BaseData<HasId> = T> {
@ -167,13 +167,11 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
defaultTimewindowInterval = historyInterval(DAY); defaultTimewindowInterval = historyInterval(DAY);
entityType: EntityType = null; entityType: EntityType = null;
tableTitle = ''; tableTitle = '';
hideTableTitle = false;
selectionEnabled = true; selectionEnabled = true;
searchEnabled = true; searchEnabled = true;
addEnabled = true; addEnabled = true;
entitiesDeleteEnabled = true; entitiesDeleteEnabled = true;
detailsPanelEnabled = true; detailsPanelEnabled = true;
hideDetailsTabs = false;
hideDetailsTabsOnEdit = true; hideDetailsTabsOnEdit = true;
rowPointer = false; rowPointer = false;
actionsColumnTitle = null; actionsColumnTitle = null;

40
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts

@ -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;
}
}

2
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html

@ -30,7 +30,7 @@
</mat-progress-bar> </mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div> <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content style="padding: 0"> <div mat-dialog-content style="padding: 0">
<tb-client #clientComponent></tb-client> <tb-client createNewDialog #clientComponent></tb-client>
</div> </div>
<div mat-dialog-actions fxLayoutAlign="end center"> <div mat-dialog-actions fxLayoutAlign="end center">
<button mat-button color="primary" <button mat-button color="primary"

53
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html

@ -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>

182
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts

@ -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);
}
}

6
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.html

@ -15,8 +15,4 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" <div tb-help="oauth2Settings" style="margin-left: -20px"></div>
class="title-container">
<span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
<div tb-help="oauth2Settings"></div>
</div>

4
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.html

@ -162,7 +162,7 @@
<div class="tb-form-row tb-standard-fields no-border no-padding column-xs"> <div class="tb-form-row tb-standard-fields no-border no-padding column-xs">
<mat-form-field class="flex"> <mat-form-field class="flex">
<mat-label translate>admin.oauth2.scope</mat-label> <mat-label translate>admin.oauth2.scope</mat-label>
<mat-chip-grid #scopeList [disabled]="!isEdit"> <mat-chip-grid #scopeList [disabled]="!this.createNewDialog && !(this.isEdit || this.isAdd)">
<mat-chip-row *ngFor="let scope of entityForm.get('scope').value; let k = index; trackBy: trackByParams" <mat-chip-row *ngFor="let scope of entityForm.get('scope').value; let k = index; trackBy: trackByParams"
removable (removed)="removeScope(k, entityForm)"> removable (removed)="removeScope(k, entityForm)">
{{scope}} {{scope}}
@ -239,7 +239,7 @@
</div> </div>
<div class="tb-form-row tb-standard-fields no-border no-padding column-xs"> <div class="tb-form-row tb-standard-fields no-border no-padding column-xs">
<mat-form-field class="flex"> <mat-form-field class="flex">
<mat-label translate>admin.oauth2.first-name-attribute-key</mat-label> <mat-label translate>admin.oauth2.tenant-name-strategy</mat-label>
<mat-select formControlName="tenantNameStrategy"> <mat-select formControlName="tenantNameStrategy">
<mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies" <mat-option *ngFor="let tenantNameStrategy of tenantNameStrategies"
[value]="tenantNameStrategy"> [value]="tenantNameStrategy">

39
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectorRef, Component, Inject, Optional } from '@angular/core'; import { ChangeDetectorRef, Component, Inject, Input, Optional } from '@angular/core';
import { EntityComponent } from '@home/components/entity/entity.component'; import { EntityComponent } from '@home/components/entity/entity.component';
import { import {
ClientAuthenticationMethod, ClientAuthenticationMethod,
@ -47,6 +47,7 @@ import { Subscription } from 'rxjs';
import { MatChipInputEvent } from '@angular/material/chips'; import { MatChipInputEvent } from '@angular/material/chips';
import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { coerceBoolean } from '@app/shared/decorators/coercion';
@Component({ @Component({
selector: 'tb-client', selector: 'tb-client',
@ -55,6 +56,10 @@ import { PageLink } from '@shared/models/page/page-link';
}) })
export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAuth2ClientInfo> { export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAuth2ClientInfo> {
@Input()
@coerceBoolean()
readonly createNewDialog = false;
templateProvider = ['Custom']; templateProvider = ['Custom'];
private templates = new Map<string, OAuth2ClientRegistrationTemplate>(); private templates = new Map<string, OAuth2ClientRegistrationTemplate>();
private defaultProvider = { private defaultProvider = {
@ -99,8 +104,6 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
platformTypeTranslations = platformTypeTranslations; platformTypeTranslations = platformTypeTranslations;
generalSettingsMode = true; generalSettingsMode = true;
readonly isDialog = false;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected translate: TranslateService, protected translate: TranslateService,
private oauth2Service: OAuth2Service, private oauth2Service: OAuth2Service,
@ -173,28 +176,7 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
activateUser: entity.mapperConfig.activateUser, activateUser: entity.mapperConfig.activateUser,
type: entity.mapperConfig.type type: entity.mapperConfig.type
} }
}, {emitEvent: false}); });
const mapperConfig = this.entityForm.get('mapperConfig') as UntypedFormGroup;
if (entity.mapperConfig.basic) {
mapperConfig.removeControl('custom', {emitEvent: false});
if (mapperConfig.get('basic')) {
this.entityForm.get('mapperConfig').patchValue({
basic: entity.mapperConfig.basic
});
} else {
mapperConfig.addControl('basic', this.formBasicGroup(entity.mapperConfig.basic));
}
} else {
mapperConfig.removeControl('basic', {emitEvent: false});
if (mapperConfig.get('custom')) {
this.entityForm.get('mapperConfig').patchValue({
custom: entity.mapperConfig.custom
});
} else {
mapperConfig.addControl('custom', this.formCustomGroup(entity.mapperConfig.custom));
}
}
const scopeControls = this.entityForm.get('scope') as UntypedFormArray; const scopeControls = this.entityForm.get('scope') as UntypedFormArray;
if (entity.scope.length === scopeControls.length) { if (entity.scope.length === scopeControls.length) {
@ -290,7 +272,6 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
private changeMapperConfigType(control: AbstractControl, type: MapperType, predefinedValue?: OAuth2MapperConfig) { private changeMapperConfigType(control: AbstractControl, type: MapperType, predefinedValue?: OAuth2MapperConfig) {
const mapperConfig = control.get('mapperConfig') as UntypedFormGroup; const mapperConfig = control.get('mapperConfig') as UntypedFormGroup;
if (type === MapperType.CUSTOM) { if (type === MapperType.CUSTOM) {
mapperConfig.removeControl('basic');
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
} else { } else {
mapperConfig.removeControl('custom'); mapperConfig.removeControl('custom');
@ -301,7 +282,7 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
mapperConfig.get('basic.emailAttributeKey').disable(); mapperConfig.get('basic.emailAttributeKey').disable();
mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false}); mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false});
} else { } else {
if (this.isEdit) { if (this.createNewDialog || this.isEdit || this.isAdd) {
mapperConfig.get('basic.emailAttributeKey').enable(); mapperConfig.get('basic.emailAttributeKey').enable();
} }
} }
@ -316,7 +297,7 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)], password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)],
sendToken: [isDefinedAndNotNull(mapperConfigCustom?.sendToken) ? mapperConfigCustom.sendToken : false] sendToken: [isDefinedAndNotNull(mapperConfigCustom?.sendToken) ? mapperConfigCustom.sendToken : false]
}); });
if (!this.isEdit) { if (!this.createNewDialog && !(this.isEdit || this.isAdd)) {
customGroup.disable(); customGroup.disable();
} }
return customGroup; return customGroup;
@ -345,7 +326,7 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false]
}); });
if (!this.isEdit) { if (!this.createNewDialog && !(this.isEdit || this.isAdd)) {
basicGroup.disable(); basicGroup.disable();
} }

5
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/clients-table-config.resolver.ts

@ -47,7 +47,6 @@ export class ClientsTableConfigResolver implements Resolve<EntityTableConfig<OAu
private utilsService: UtilsService, private utilsService: UtilsService,
private oauth2Service: OAuth2Service) { private oauth2Service: OAuth2Service) {
this.config.tableTitle = this.translate.instant('admin.oauth2.clients'); this.config.tableTitle = this.translate.instant('admin.oauth2.clients');
this.config.hideTableTitle = true;
this.config.selectionEnabled = false; this.config.selectionEnabled = false;
this.config.entityType = EntityType.OAUTH2_CLIENT; this.config.entityType = EntityType.OAUTH2_CLIENT;
this.config.rowPointer = true; this.config.rowPointer = true;
@ -60,7 +59,6 @@ export class ClientsTableConfigResolver implements Resolve<EntityTableConfig<OAu
}; };
this.config.entityComponent = ClientComponent; this.config.entityComponent = ClientComponent;
this.config.headerComponent = ClientTableHeaderComponent; this.config.headerComponent = ClientTableHeaderComponent;
this.config.hideDetailsTabs = true;
this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'}; this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'};
this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.config.displayPagination = false; this.config.displayPagination = false;
@ -68,7 +66,8 @@ export class ClientsTableConfigResolver implements Resolve<EntityTableConfig<OAu
this.config.columns.push( this.config.columns.push(
new DateEntityTableColumn<OAuth2ClientInfo>('createdTime', 'common.created-time', this.datePipe, '170px'), new DateEntityTableColumn<OAuth2ClientInfo>('createdTime', 'common.created-time', this.datePipe, '170px'),
new EntityTableColumn<OAuth2ClientInfo>('title', 'admin.oauth2.provider', '170px'), new EntityTableColumn<OAuth2ClientInfo>('title', 'admin.oauth2.title', '170px'),
new EntityTableColumn<OAuth2ClientInfo>('providerName', 'admin.oauth2.provider', '170px'),
new EntityTableColumn<OAuth2ClientInfo>('platforms', 'admin.oauth2.allowed-platforms', '100%', new EntityTableColumn<OAuth2ClientInfo>('platforms', 'admin.oauth2.allowed-platforms', '100%',
(clientInfo) => { (clientInfo) => {
return clientInfo.platforms && clientInfo.platforms.length ? return clientInfo.platforms && clientInfo.platforms.length ?

16
ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts

@ -17,9 +17,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { import {
ClientChipsEntityTableColumn,
DateEntityTableColumn, DateEntityTableColumn,
EntityActionTableColumn, EntityActionTableColumn,
EntityChipsEntityTableColumn,
EntityTableColumn, EntityTableColumn,
EntityTableConfig EntityTableConfig
} from '@home/models/entity/entities-table-config.models'; } from '@home/models/entity/entities-table-config.models';
@ -44,7 +44,6 @@ export class DomainTableConfigResolver implements Resolve<EntityTableConfig<Doma
private utilsService: UtilsService, private utilsService: UtilsService,
private domainService: DomainService) { private domainService: DomainService) {
this.config.tableTitle = this.translate.instant('admin.oauth2.domains'); this.config.tableTitle = this.translate.instant('admin.oauth2.domains');
this.config.hideTableTitle = true;
this.config.selectionEnabled = false; this.config.selectionEnabled = false;
this.config.entityType = EntityType.DOMAIN; this.config.entityType = EntityType.DOMAIN;
this.config.rowPointer = true; this.config.rowPointer = true;
@ -52,8 +51,7 @@ export class DomainTableConfigResolver implements Resolve<EntityTableConfig<Doma
this.config.entityResources = entityTypeResources.get(EntityType.DOMAIN); this.config.entityResources = entityTypeResources.get(EntityType.DOMAIN);
this.config.entityComponent = DomainComponent; this.config.entityComponent = DomainComponent;
this.config.headerComponent = DomainTableHeaderComponent; this.config.headerComponent = DomainTableHeaderComponent;
this.config.hideDetailsTabs = true; this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'};
this.config.addDialogStyle = {width: '850px', height: '688px'};
this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.config.displayPagination = false; this.config.displayPagination = false;
this.config.pageMode = false; this.config.pageMode = false;
@ -61,14 +59,14 @@ export class DomainTableConfigResolver implements Resolve<EntityTableConfig<Doma
this.config.columns.push( this.config.columns.push(
new DateEntityTableColumn<DomainInfo>('createdTime', 'common.created-time', this.datePipe, '170px'), new DateEntityTableColumn<DomainInfo>('createdTime', 'common.created-time', this.datePipe, '170px'),
new EntityTableColumn<DomainInfo>('name', 'admin.oauth2.domain-name', '170px'), new EntityTableColumn<DomainInfo>('name', 'admin.oauth2.domain-name', '170px'),
new ClientChipsEntityTableColumn<DomainInfo>('oauth2ClientInfos', 'admin.oauth2.clients', '40%'), new EntityChipsEntityTableColumn<DomainInfo>('oauth2ClientInfos', 'admin.oauth2.clients', '40%'),
new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable', new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable',
{ {
name: '', name: '',
nameFunction: (domain) => nameFunction: (domain) =>
this.translate.instant(domain.oauth2Enabled ? 'admin.oauth2.disable' : 'admin.oauth2.enable'), this.translate.instant(domain.oauth2Enabled ? 'admin.oauth2.disable' : 'admin.oauth2.enable'),
icon: 'mdi:toggle-switch', icon: 'mdi:toggle-switch',
iconFunction: (entity) => entity.oauth2Enabled ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline', iconFunction: (domain) => domain.oauth2Enabled ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event, entity) => this.toggleEnableOAuth($event, entity) onAction: ($event, entity) => this.toggleEnableOAuth($event, entity)
}), }),
@ -88,9 +86,9 @@ export class DomainTableConfigResolver implements Resolve<EntityTableConfig<Doma
this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-domain-text'); this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-domain-text');
this.config.entitiesFetchFunction = pageLink => this.domainService.getTenantDomainInfos(pageLink); this.config.entitiesFetchFunction = pageLink => this.domainService.getTenantDomainInfos(pageLink);
this.config.loadEntity = id => this.domainService.getDomainInfoById(id.id); this.config.loadEntity = id => this.domainService.getDomainInfoById(id.id);
this.config.saveEntity = (domain, originalEntity) => { this.config.saveEntity = (domain, originalDomain) => {
const clientsIds = domain.oauth2ClientInfos ? domain.oauth2ClientInfos.map(clientInfo => clientInfo.id.id) : []; const clientsIds = domain.oauth2ClientInfos as Array<string>;
if (domain.id && !isEqual(domain.oauth2ClientInfos, originalEntity.oauth2ClientInfos)) { if (domain.id && !isEqual(domain.oauth2ClientInfos.sort(), originalDomain.oauth2ClientInfos.map(info => info.id.id).sort())) {
this.domainService.updateOauth2Clients(domain.id.id, clientsIds).subscribe(); this.domainService.updateOauth2Clients(domain.id.id, clientsIds).subscribe();
} }
delete domain.oauth2ClientInfos; delete domain.oauth2ClientInfos;

5
ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.html

@ -15,7 +15,4 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container"> <div tb-help="oauth2Settings" style="margin-left: -20px"></div>
<span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
<div tb-help="oauth2Settings"></div>
</div>

8
ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html

@ -46,19 +46,19 @@
</tb-copy-button> </tb-copy-button>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="tb-form-row no-border no-padding"> <div class="tb-form-row no-border no-padding" style="margin-bottom: 22px">
<mat-slide-toggle class="mat-slide" formControlName="oauth2Enabled"> <mat-slide-toggle class="mat-slide" formControlName="oauth2Enabled">
{{ 'admin.oauth2.enable' | translate }} {{ 'admin.oauth2.enable' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<tb-client-list formControlName="oauth2ClientInfos"> <tb-entity-list [entityType]="entityType.OAUTH2_CLIENT" formControlName="oauth2ClientInfos">
<button #createTargetButton <button #createTargetButton
mat-button color="primary" matSuffix mat-button color="primary" matSuffix
[disabled]="!isEdit" [disabled]="!isEdit"
(click)="createClient($event, createTargetButton)"> (click)="createClient($event, createTargetButton)">
<span style="white-space: nowrap">{{ 'notification.create-new' | translate }}</span> <span style="white-space: nowrap">{{ 'admin.oauth2.create-new' | translate }}</span>
</button> </button>
</tb-client-list> </tb-entity-list>
<div class="tb-form-row no-border no-padding"> <div class="tb-form-row no-border no-padding">
<mat-slide-toggle class="mat-slide" formControlName="propagateToEdge"> <mat-slide-toggle class="mat-slide" formControlName="propagateToEdge">
{{ 'admin.oauth2.edge' | translate }} {{ 'admin.oauth2.edge' | translate }}

17
ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts

@ -29,6 +29,7 @@ import { isDefinedAndNotNull } from '@core/utils';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component';
import { EntityType } from '@shared/models/entity-type.models';
@Component({ @Component({
selector: 'tb-domain', selector: 'tb-domain',
@ -39,6 +40,8 @@ export class DomainComponent extends EntityComponent<DomainInfo> {
private loginProcessingUrl: string = ''; private loginProcessingUrl: string = '';
entityType = EntityType;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected translate: TranslateService, protected translate: TranslateService,
private oauth2Service: OAuth2Service, private oauth2Service: OAuth2Service,
@ -59,8 +62,8 @@ export class DomainComponent extends EntityComponent<DomainInfo> {
return this.fb.group({ return this.fb.group({
name: [entity?.name ? entity.name : '', [ name: [entity?.name ? entity.name : '', [
Validators.required, Validators.maxLength(255), Validators.pattern('^(?:\\w+(?::\\w+)?@)?[^\\s/]+(?::\\d+)?$')]], Validators.required, Validators.maxLength(255), Validators.pattern('^(?:\\w+(?::\\w+)?@)?[^\\s/]+(?::\\d+)?$')]],
oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false, oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : true,
oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos : [], oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos.map(info => info.id.id) : [],
propagateToEdge: isDefinedAndNotNull(entity?.propagateToEdge) ? entity.propagateToEdge : false propagateToEdge: isDefinedAndNotNull(entity?.propagateToEdge) ? entity.propagateToEdge : false
}); });
} }
@ -69,7 +72,7 @@ export class DomainComponent extends EntityComponent<DomainInfo> {
this.entityForm.patchValue({ this.entityForm.patchValue({
name: entity.name, name: entity.name,
oauth2Enabled: entity.oauth2Enabled, oauth2Enabled: entity.oauth2Enabled,
oauth2ClientInfos: entity.oauth2ClientInfos, oauth2ClientInfos: entity.oauth2ClientInfos?.map(info => info.id.id),
propagateToEdge: entity.propagateToEdge propagateToEdge: entity.propagateToEdge
}) })
} }
@ -84,20 +87,18 @@ export class DomainComponent extends EntityComponent<DomainInfo> {
$event.stopPropagation(); $event.stopPropagation();
$event.preventDefault(); $event.preventDefault();
} }
button._elementRef.nativeElement.blur();
this.dialog.open<ClientDialogComponent>(ClientDialogComponent, { this.dialog.open<ClientDialogComponent>(ClientDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {} data: {}
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((client) => {
if (res) { if (client) {
const formValue = this.entityForm.get('oauth2ClientInfos').value ? const formValue = this.entityForm.get('oauth2ClientInfos').value ?
[...this.entityForm.get('oauth2ClientInfos').value] : []; [...this.entityForm.get('oauth2ClientInfos').value] : [];
formValue.push({id: res.id, title: res.title}); formValue.push(client.id.id);
this.entityForm.get('oauth2ClientInfos').patchValue(formValue); this.entityForm.get('oauth2ClientInfos').patchValue(formValue);
this.entityForm.get('oauth2ClientInfos').markAsDirty(); this.entityForm.get('oauth2ClientInfos').markAsDirty();
this.entityForm.updateValueAndValidity();
} }
}); });
} }

14
ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts

@ -18,9 +18,9 @@ import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { import {
CellActionDescriptorType, CellActionDescriptorType,
ClientChipsEntityTableColumn,
DateEntityTableColumn, DateEntityTableColumn,
EntityActionTableColumn, EntityActionTableColumn,
EntityChipsEntityTableColumn,
EntityTableColumn, EntityTableColumn,
EntityTableConfig EntityTableConfig
} from '@home/models/entity/entities-table-config.models'; } from '@home/models/entity/entities-table-config.models';
@ -45,7 +45,6 @@ export class MobileAppTableConfigResolver implements Resolve<EntityTableConfig<M
private utilsService: UtilsService, private utilsService: UtilsService,
private mobileAppService: MobileAppService) { private mobileAppService: MobileAppService) {
this.config.tableTitle = this.translate.instant('admin.oauth2.mobile-apps'); this.config.tableTitle = this.translate.instant('admin.oauth2.mobile-apps');
this.config.hideTableTitle = true;
this.config.selectionEnabled = false; this.config.selectionEnabled = false;
this.config.entityType = EntityType.MOBILE_APP; this.config.entityType = EntityType.MOBILE_APP;
this.config.rowPointer = true; this.config.rowPointer = true;
@ -53,8 +52,7 @@ export class MobileAppTableConfigResolver implements Resolve<EntityTableConfig<M
this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP); this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP);
this.config.entityComponent = MobileAppComponent; this.config.entityComponent = MobileAppComponent;
this.config.headerComponent = MobileAppTableHeaderComponent; this.config.headerComponent = MobileAppTableHeaderComponent;
this.config.hideDetailsTabs = true; this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'};
this.config.addDialogStyle = {width: '850px', height: '688px'};
this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.config.displayPagination = false; this.config.displayPagination = false;
this.config.pageMode = false; this.config.pageMode = false;
@ -77,7 +75,7 @@ export class MobileAppTableConfigResolver implements Resolve<EntityTableConfig<M
onAction: ($event, entity) => entity.appSecret, onAction: ($event, entity) => entity.appSecret,
type: CellActionDescriptorType.COPY_BUTTON type: CellActionDescriptorType.COPY_BUTTON
}), }),
new ClientChipsEntityTableColumn<MobileAppInfo>('oauth2ClientInfos', 'admin.oauth2.clients', '20%'), new EntityChipsEntityTableColumn<MobileAppInfo>('oauth2ClientInfos', 'admin.oauth2.clients', '20%'),
new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable', new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable',
{ {
name: '', name: '',
@ -94,9 +92,9 @@ export class MobileAppTableConfigResolver implements Resolve<EntityTableConfig<M
this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-mobile-app-text'); this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-mobile-app-text');
this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink); this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink);
this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id); this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id);
this.config.saveEntity = (mobileApp, originalEntity) => { this.config.saveEntity = (mobileApp, originalMobileApp) => {
const clientsIds = mobileApp.oauth2ClientInfos ? mobileApp.oauth2ClientInfos.map(clientInfo => clientInfo.id.id) : []; const clientsIds = mobileApp.oauth2ClientInfos as Array<string>;
if (mobileApp.id && !isEqual(mobileApp.oauth2ClientInfos, originalEntity.oauth2ClientInfos)) { if (mobileApp.id && !isEqual(mobileApp.oauth2ClientInfos.sort(), originalMobileApp.oauth2ClientInfos.map(info => info.id.id).sort())) {
this.mobileAppService.updateOauth2Clients(mobileApp.id.id, clientsIds).subscribe(); this.mobileAppService.updateOauth2Clients(mobileApp.id.id, clientsIds).subscribe();
} }
delete mobileApp.oauth2ClientInfos; delete mobileApp.oauth2ClientInfos;

5
ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html

@ -15,7 +15,4 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" fxLayoutAlign="start center" fxLayout.xs="column" fxLayoutAlign.xs="center start" class="title-container"> <div tb-help="oauth2Settings" style="margin-left: -20px"></div>
<span *ngIf="entitiesTableConfig.tableTitle" class="tb-entity-table-title">{{ entitiesTableConfig.tableTitle }}</span>
<div tb-help="oauth2Settings"></div>
</div>

6
ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html

@ -57,13 +57,13 @@
{{ 'admin.oauth2.enable' | translate }} {{ 'admin.oauth2.enable' | translate }}
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<tb-client-list formControlName="oauth2ClientInfos"> <tb-entity-list [entityType]="entityType.OAUTH2_CLIENT" formControlName="oauth2ClientInfos">
<button #createTargetButton <button #createTargetButton
mat-button color="primary" matSuffix mat-button color="primary" matSuffix
[disabled]="!isEdit" [disabled]="!isEdit"
(click)="createClient($event, createTargetButton)"> (click)="createClient($event, createTargetButton)">
<span style="white-space: nowrap">{{ 'notification.create-new' | translate }}</span> <span style="white-space: nowrap">{{ 'admin.oauth2.create-new' | translate }}</span>
</button> </button>
</tb-client-list> </tb-entity-list>
</section> </section>

20
ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts

@ -25,10 +25,11 @@ import { TranslateService } from '@ngx-translate/core';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull, randomAlphanumeric } from '@core/utils';
import { MatButton } from '@angular/material/button'; import { MatButton } from '@angular/material/button';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component';
import { EntityType } from '@shared/models/entity-type.models';
@Component({ @Component({
selector: 'tb-mobile-app', selector: 'tb-mobile-app',
@ -37,6 +38,8 @@ import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-d
}) })
export class MobileAppComponent extends EntityComponent<MobileAppInfo> { export class MobileAppComponent extends EntityComponent<MobileAppInfo> {
entityType = EntityType;
constructor(protected store: Store<AppState>, constructor(protected store: Store<AppState>,
protected translate: TranslateService, protected translate: TranslateService,
private oauth2Service: OAuth2Service, private oauth2Service: OAuth2Service,
@ -52,9 +55,10 @@ export class MobileAppComponent extends EntityComponent<MobileAppInfo> {
buildForm(entity: MobileAppInfo): UntypedFormGroup { buildForm(entity: MobileAppInfo): UntypedFormGroup {
return this.fb.group({ return this.fb.group({
pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required]], pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required]],
appSecret: [entity?.appSecret ? entity.appSecret : '', [Validators.required, this.base64Format]], appSecret: [entity?.appSecret ? entity.appSecret : btoa(randomAlphanumeric(64)),
[Validators.required, this.base64Format]],
oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false, oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false,
oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos : [] oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos.map(info => info.id.id) : []
}); });
} }
@ -63,7 +67,7 @@ export class MobileAppComponent extends EntityComponent<MobileAppInfo> {
pkgName: entity.pkgName, pkgName: entity.pkgName,
appSecret: entity.appSecret, appSecret: entity.appSecret,
oauth2Enabled: entity.oauth2Enabled, oauth2Enabled: entity.oauth2Enabled,
oauth2ClientInfos: entity.oauth2ClientInfos oauth2ClientInfos: entity.oauth2ClientInfos?.map(info => info.id.id)
}) })
} }
@ -72,20 +76,18 @@ export class MobileAppComponent extends EntityComponent<MobileAppInfo> {
$event.stopPropagation(); $event.stopPropagation();
$event.preventDefault(); $event.preventDefault();
} }
button._elementRef.nativeElement.blur();
this.dialog.open<ClientDialogComponent>(ClientDialogComponent, { this.dialog.open<ClientDialogComponent>(ClientDialogComponent, {
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {} data: {}
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((client) => {
if (res) { if (client) {
const formValue = this.entityForm.get('oauth2ClientInfos').value ? const formValue = this.entityForm.get('oauth2ClientInfos').value ?
[...this.entityForm.get('oauth2ClientInfos').value] : []; [...this.entityForm.get('oauth2ClientInfos').value] : [];
formValue.push({id: res.id, title: res.title}); formValue.push(client.id.id);
this.entityForm.get('oauth2ClientInfos').patchValue(formValue); this.entityForm.get('oauth2ClientInfos').patchValue(formValue);
this.entityForm.get('oauth2ClientInfos').markAsDirty(); this.entityForm.get('oauth2ClientInfos').markAsDirty();
this.entityForm.updateValueAndValidity();
} }
}); });
} }

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

@ -22,7 +22,6 @@ import { HomeComponentsModule } from '@home/components/home-components.module';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ClientTableHeaderComponent } from '@home/pages/admin/oauth2/clients/client-table-header.component'; import { ClientTableHeaderComponent } from '@home/pages/admin/oauth2/clients/client-table-header.component';
import { DomainComponent } from '@home/pages/admin/oauth2/domains/domain.component'; import { DomainComponent } from '@home/pages/admin/oauth2/domains/domain.component';
import { ClientListComponent } from '@home/pages/admin/oauth2/clients/client-list.component';
import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component';
import { DomainTableHeaderComponent } from '@home/pages/admin/oauth2/domains/domain-table-header.component'; import { DomainTableHeaderComponent } from '@home/pages/admin/oauth2/domains/domain-table-header.component';
import { MobileAppComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app.component'; import { MobileAppComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app.component';
@ -32,7 +31,6 @@ import { MobileAppTableHeaderComponent } from '@home/pages/admin/oauth2/mobile-a
declarations: [ declarations: [
ClientComponent, ClientComponent,
ClientDialogComponent, ClientDialogComponent,
ClientListComponent,
ClientTableHeaderComponent, ClientTableHeaderComponent,
DomainComponent, DomainComponent,
DomainTableHeaderComponent, DomainTableHeaderComponent,

4
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -609,7 +609,9 @@ export const baseDetailsPageByEntityType = new Map<EntityType, string>([
[EntityType.QUEUE, '/settings/queues'], [EntityType.QUEUE, '/settings/queues'],
[EntityType.WIDGETS_BUNDLE, '/resources/widgets-library/widgets-bundles/details'], [EntityType.WIDGETS_BUNDLE, '/resources/widgets-library/widgets-bundles/details'],
[EntityType.WIDGET_TYPE, '/resources/widgets-library/widget-types/details'], [EntityType.WIDGET_TYPE, '/resources/widgets-library/widget-types/details'],
[EntityType.OAUTH2_CLIENT, '/security-settings/oauth2/clients/details'] [EntityType.OAUTH2_CLIENT, '/security-settings/oauth2/clients/details'],
[EntityType.DOMAIN, '/security-settings/oauth2/clients/details'],
[EntityType.MOBILE_APP, '/security-settings/oauth2/clients/details']
]); ]);
export interface EntitySubtype { export interface EntitySubtype {

7
ui-ngx/src/app/shared/models/oauth2.models.ts

@ -81,11 +81,11 @@ export interface Domain extends BaseData<DomainId>, HasTenantId {
} }
export interface HasOauth2Clients { export interface HasOauth2Clients {
oauth2ClientInfos?: Array<OAuth2ClientInfo>; oauth2ClientInfos?: Array<OAuth2ClientInfo> | Array<string>;
} }
export interface DomainInfo extends Domain, HasOauth2Clients { export interface DomainInfo extends Domain, HasOauth2Clients {
oauth2ClientInfos?: Array<OAuth2ClientInfo>; oauth2ClientInfos?: Array<OAuth2ClientInfo> | Array<string>;
} }
export interface MobileApp extends BaseData<MobileAppId>, HasTenantId { export interface MobileApp extends BaseData<MobileAppId>, HasTenantId {
@ -96,7 +96,7 @@ export interface MobileApp extends BaseData<MobileAppId>, HasTenantId {
} }
export interface MobileAppInfo extends MobileApp, HasOauth2Clients { export interface MobileAppInfo extends MobileApp, HasOauth2Clients {
oauth2ClientInfos?: Array<OAuth2ClientInfo>; oauth2ClientInfos?: Array<OAuth2ClientInfo> | Array<string>;
} }
export interface OAuth2Client extends BaseData<OAuth2ClientId>, HasTenantId { export interface OAuth2Client extends BaseData<OAuth2ClientId>, HasTenantId {
@ -167,6 +167,7 @@ export const platformTypeTranslations = new Map<PlatformType, string>(
export interface OAuth2ClientInfo extends BaseData<OAuth2ClientId> { export interface OAuth2ClientInfo extends BaseData<OAuth2ClientId> {
title: string; title: string;
providerName: string;
platforms?: Array<PlatformType>; platforms?: Array<PlatformType>;
} }

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

@ -243,6 +243,7 @@
"no-mobile-apps": "Search mobile applications", "no-mobile-apps": "Search mobile applications",
"search-mobile-apps": "No mobile applications found", "search-mobile-apps": "No mobile applications found",
"send-token": "Send token", "send-token": "Send token",
"create-new": "Create new",
"client-authentication-method": "Client authentication method", "client-authentication-method": "Client authentication method",
"client-id": "Client ID", "client-id": "Client ID",
"client-id-required": "Client ID is required.", "client-id-required": "Client ID is required.",

Loading…
Cancel
Save