Browse Source

Merge pull request #24516 from abpframework/fix/resource-based-permission-modal-problems

Angular - Fixing resource based permission modal problems
pull/24525/head
Yağmur Çelik 1 month ago
committed by GitHub
parent
commit
306ff239fb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 80
      npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html
  2. 9
      npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss
  3. 232
      npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts
  4. 52
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.html
  5. 14
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.html
  6. 90
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts
  7. 41
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-form/resource-permission-form.component.html
  8. 107
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html
  9. 385
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts

80
npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.html

@ -1,45 +1,61 @@
<div class="abp-lookup-container position-relative">
@if (label()) {
<label class="form-label">{{ label() | abpLocalization }}</label>
<label class="form-label">{{ label() | abpLocalization }}</label>
}
<div class="input-group">
<input type="text" class="form-control" [placeholder]="placeholder() | abpLocalization" [ngModel]="displayValue()"
(ngModelChange)="onSearchInput($event)" (focus)="onSearchFocus()" (blur)="onSearchBlur($event)"
[disabled]="disabled()" />
<input
type="text"
class="form-control"
[placeholder]="placeholder() | abpLocalization"
[ngModel]="displayValue()"
(ngModelChange)="onSearchInput($event)"
(focus)="onSearchFocus()"
(blur)="onSearchBlur($event)"
[disabled]="disabled()"
/>
@if (displayValue() && !disabled()) {
<button type="button" class="btn btn-outline-secondary" (mousedown)="clearSelection()" tabindex="-1">
<i class="fa fa-times"></i>
</button>
<button
type="button"
class="btn btn-outline-secondary"
(mousedown)="clearSelection()"
tabindex="-1"
>
<i class="fa fa-times"></i>
</button>
}
</div>
@if (showDropdown() && !disabled()) {
<div class="abp-lookup-dropdown list-group position-absolute w-100 shadow">
@if (isLoading()) {
<div class="list-group-item text-center py-3">
<i class="fa fa-spinner fa-spin me-2"></i>
{{ 'AbpUi::Loading' | abpLocalization }}
</div>
} @else if (searchResults().length > 0) {
@for (item of searchResults(); track item.key) {
<button type="button" class="list-group-item list-group-item-action" (mousedown)="selectItem(item)">
@if (itemTemplate()) {
<ng-container *ngTemplateOutlet="itemTemplate()!; context: { $implicit: item }" />
} @else {
{{ getDisplayValue(item) }}
<div class="abp-lookup-dropdown list-group position-absolute w-100">
@if (isLoading()) {
<div class="list-group-item text-center py-3">
<i class="fa fa-spinner fa-spin me-2"></i>
{{ 'AbpUi::Loading' | abpLocalization }}
</div>
} @else if (searchResults().length > 0) {
@for (item of searchResults(); track item.key) {
<button
type="button"
class="list-group-item list-group-item-action"
(mousedown)="selectItem(item)"
>
@if (itemTemplate()) {
<ng-container *ngTemplateOutlet="itemTemplate()!; context: { $implicit: item }" />
} @else {
{{ getDisplayValue(item) }}
}
</button>
}
} @else if (displayValue()) {
@if (noResultsTemplate()) {
<ng-container *ngTemplateOutlet="noResultsTemplate()!" />
} @else {
<div class="list-group-item text-muted">
{{ 'AbpUi::NoDataAvailableInDatatable' | abpLocalization }}
</div>
}
}
</button>
}
} @else if (displayValue()) {
@if (noResultsTemplate()) {
<ng-container *ngTemplateOutlet="noResultsTemplate()!" />
} @else {
<div class="list-group-item text-muted">
{{ 'AbpUi::NoRecordsFound' | abpLocalization }}
</div>
}
}
</div>
}
</div>
</div>

9
npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.scss

@ -1,5 +1,8 @@
.abp-lookup-dropdown {
z-index: 1050;
max-height: 200px;
overflow-y: auto;
z-index: 1060;
max-height: 200px;
overflow-y: auto;
top: 100%;
margin-top: 0.25rem;
background-color: var(--lpx-content-bg);
}

232
npm/ng-packs/packages/components/lookup/src/lib/lookup-search.component.ts

@ -1,15 +1,15 @@
import {
Component,
input,
output,
model,
signal,
OnInit,
ChangeDetectionStrategy,
TemplateRef,
contentChild,
DestroyRef,
inject,
Component,
input,
output,
model,
signal,
OnInit,
ChangeDetectionStrategy,
TemplateRef,
contentChild,
DestroyRef,
inject,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common';
@ -18,119 +18,123 @@ import { LocalizationPipe } from '@abp/ng.core';
import { Subject, Observable, debounceTime, distinctUntilChanged, of, finalize } from 'rxjs';
export interface LookupItem {
key: string;
displayName: string;
[key: string]: unknown;
key: string;
displayName: string;
[key: string]: unknown;
}
export type LookupSearchFn<T = LookupItem> = (filter: string) => Observable<T[]>;
@Component({
selector: 'abp-lookup-search',
templateUrl: './lookup-search.component.html',
styleUrl: './lookup-search.component.scss',
imports: [CommonModule, FormsModule, LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'abp-lookup-search',
templateUrl: './lookup-search.component.html',
styleUrl: './lookup-search.component.scss',
imports: [CommonModule, FormsModule, LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LookupSearchComponent<T extends LookupItem = LookupItem> implements OnInit {
private readonly destroyRef = inject(DestroyRef);
readonly label = input<string>();
readonly placeholder = input<string>('');
readonly debounceTime = input<number>(300);
readonly minSearchLength = input<number>(0);
readonly displayKey = input<keyof T>('displayName' as keyof T);
readonly valueKey = input<keyof T>('key' as keyof T);
readonly disabled = input<boolean>(false);
readonly searchFn = input<LookupSearchFn<T>>(() => of([]));
readonly selectedValue = model<string>('');
readonly displayValue = model<string>('');
readonly itemSelected = output<T>();
readonly searchChanged = output<string>();
readonly itemTemplate = contentChild<TemplateRef<{ $implicit: T }>>('itemTemplate');
readonly noResultsTemplate = contentChild<TemplateRef<void>>('noResultsTemplate');
readonly searchResults = signal<T[]>([]);
readonly showDropdown = signal(false);
readonly isLoading = signal(false);
private readonly searchSubject = new Subject<string>();
ngOnInit() {
this.searchSubject.pipe(
debounceTime(this.debounceTime()),
distinctUntilChanged(),
takeUntilDestroyed(this.destroyRef)
).subscribe(filter => {
this.performSearch(filter);
});
}
onSearchInput(filter: string) {
this.displayValue.set(filter);
this.showDropdown.set(true);
this.searchChanged.emit(filter);
if (filter.length >= this.minSearchLength()) {
this.searchSubject.next(filter);
} else {
this.searchResults.set([]);
}
}
onSearchFocus() {
this.showDropdown.set(true);
const currentFilter = this.displayValue() || '';
if (currentFilter.length >= this.minSearchLength()) {
this.performSearch(currentFilter);
}
}
onSearchBlur(event: FocusEvent) {
const relatedTarget = event.relatedTarget as HTMLElement;
if (!relatedTarget?.closest('.abp-lookup-dropdown')) {
this.showDropdown.set(false);
}
}
selectItem(item: T) {
const displayKeyValue = String(item[this.displayKey()] ?? '');
const valueKeyValue = String(item[this.valueKey()] ?? '');
this.displayValue.set(displayKeyValue);
this.selectedValue.set(valueKeyValue);
this.searchResults.set([]);
this.showDropdown.set(false);
this.itemSelected.emit(item);
}
clearSelection() {
this.displayValue.set('');
this.selectedValue.set('');
this.searchResults.set([]);
private readonly destroyRef = inject(DestroyRef);
readonly label = input<string>();
readonly placeholder = input<string>('');
readonly debounceTime = input<number>(300);
readonly minSearchLength = input<number>(0);
readonly displayKey = input<keyof T>('displayName' as keyof T);
readonly valueKey = input<keyof T>('key' as keyof T);
readonly disabled = input<boolean>(false);
readonly searchFn = input<LookupSearchFn<T>>(() => of([]));
readonly selectedValue = model<string>('');
readonly displayValue = model<string>('');
readonly itemSelected = output<T>();
readonly searchChanged = output<string>();
readonly itemTemplate = contentChild<TemplateRef<{ $implicit: T }>>('itemTemplate');
readonly noResultsTemplate = contentChild<TemplateRef<void>>('noResultsTemplate');
readonly searchResults = signal<T[]>([]);
readonly showDropdown = signal(true);
readonly isLoading = signal(false);
private readonly searchSubject = new Subject<string>();
ngOnInit() {
this.searchSubject
.pipe(
debounceTime(this.debounceTime()),
distinctUntilChanged(),
takeUntilDestroyed(this.destroyRef),
)
.subscribe(filter => {
this.performSearch(filter);
});
}
onSearchInput(filter: string) {
this.displayValue.set(filter);
this.showDropdown.set(true);
this.searchChanged.emit(filter);
if (filter.length >= this.minSearchLength()) {
this.searchSubject.next(filter);
} else {
this.searchResults.set([]);
}
}
private performSearch(filter: string) {
this.isLoading.set(true);
this.searchFn()(filter).pipe(
takeUntilDestroyed(this.destroyRef),
finalize(() => this.isLoading.set(false))
).subscribe({
next: results => {
this.searchResults.set(results);
},
error: () => {
this.searchResults.set([]);
}
});
onSearchFocus() {
this.showDropdown.set(true);
const currentFilter = this.displayValue() || '';
if (currentFilter.length >= this.minSearchLength()) {
this.performSearch(currentFilter);
}
}
getDisplayValue(item: T): string {
return String(item[this.displayKey()] ?? item[this.valueKey()] ?? '');
onSearchBlur(event: FocusEvent) {
const relatedTarget = event.relatedTarget as HTMLElement;
if (!relatedTarget?.closest('.abp-lookup-dropdown')) {
this.showDropdown.set(false);
}
}
selectItem(item: T) {
const displayKeyValue = String(item[this.displayKey()] ?? '');
const valueKeyValue = String(item[this.valueKey()] ?? '');
this.displayValue.set(displayKeyValue);
this.selectedValue.set(valueKeyValue);
this.searchResults.set([]);
this.showDropdown.set(false);
this.itemSelected.emit(item);
}
clearSelection() {
this.displayValue.set('');
this.selectedValue.set('');
this.searchResults.set([]);
}
private performSearch(filter: string) {
this.isLoading.set(true);
this.searchFn()(filter)
.pipe(
takeUntilDestroyed(this.destroyRef),
finalize(() => this.isLoading.set(false)),
)
.subscribe({
next: results => {
this.searchResults.set(results);
},
error: () => {
this.searchResults.set([]);
},
});
}
getDisplayValue(item: T): string {
return String(item[this.displayKey()] ?? item[this.valueKey()] ?? '');
}
}

52
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.html

@ -1,25 +1,33 @@
<div class="mb-3">
@if (showTitle()) {
@if (showTitle()) {
<h5>{{ title() | abpLocalization }}</h5>
}
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="grantAll-{{ idPrefix() }}"
[checked]="state.allPermissionsSelected()"
(change)="state.toggleAllPermissions(!state.allPermissionsSelected())" />
<label class="form-check-label" for="grantAll-{{ idPrefix() }}">
{{ 'AbpPermissionManagement::GrantAllResourcePermissions' | abpLocalization }}
}
<div class="form-check form-switch mb-2">
<input
class="form-check-input"
type="checkbox"
id="grantAll-{{ idPrefix() }}"
[checked]="state.allPermissionsSelected()"
(change)="state.toggleAllPermissions(!state.allPermissionsSelected())"
/>
<label class="form-check-label" for="grantAll-{{ idPrefix() }}">
{{ 'AbpPermissionManagement::GrantAllResourcePermissions' | abpLocalization }}
</label>
</div>
<div class="abp-permission-list-container border rounded p-3">
@for (perm of permissions(); track perm.name) {
<div class="form-check">
<input
class="form-check-input"
type="checkbox"
id="perm-{{ idPrefix() }}-{{ perm.name }}"
[checked]="state.isPermissionSelected(perm.name || '')"
(change)="state.togglePermission(perm.name || '')"
/>
<label class="form-check-label" for="perm-{{ idPrefix() }}-{{ perm.name }}">
{{ perm.displayName }}
</label>
</div>
<div class="abp-permission-list-container border rounded p-3">
@for (perm of permissions(); track perm.name) {
<div class="form-check">
<input class="form-check-input" type="checkbox" id="perm-{{ idPrefix() }}-{{ perm.name }}"
[checked]="state.isPermissionSelected(perm.name || '')"
(change)="state.togglePermission(perm.name || '')" />
<label class="form-check-label" for="perm-{{ idPrefix() }}-{{ perm.name }}">
{{ perm.displayName }}
</label>
</div>
}
</div>
</div>
</div>
}
</div>
</div>

14
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.html

@ -1,4 +1,10 @@
<abp-lookup-search label="AbpPermissionManagement::SearchProviderKey"
placeholder="AbpPermissionManagement::SearchProviderKey" [searchFn]="searchFn" [displayValue]="state.searchFilter()"
(displayValueChange)="state.searchFilter.set($event)" [selectedValue]="state.selectedProviderKey()"
(selectedValueChange)="state.selectedProviderKey.set($event)" (itemSelected)="onItemSelected($event)" />
<abp-lookup-search
label="AbpPermissionManagement::SearchProviderKey"
placeholder="AbpPermissionManagement::SearchProviderKey"
[searchFn]="searchFn"
[displayValue]="state.searchFilter()"
(displayValueChange)="state.searchFilter.set($event)"
[selectedValue]="state.selectedProviderKey()"
(selectedValueChange)="state.selectedProviderKey.set($event)"
(itemSelected)="onItemSelected($event)"
/>

90
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts

@ -1,4 +1,11 @@
import { Component, input, inject, OnInit, ChangeDetectionStrategy, DestroyRef } from '@angular/core';
import {
Component,
input,
inject,
OnInit,
ChangeDetectionStrategy,
DestroyRef,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PermissionsService } from '@abp/ng.permission-management/proxy';
import { LookupSearchComponent, LookupItem } from '@abp/ng.components/lookup';
@ -6,56 +13,55 @@ import { Observable, map } from 'rxjs';
import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
interface ProviderKeyLookupItem extends LookupItem {
providerKey: string;
providerDisplayName?: string;
providerKey: string;
providerDisplayName?: string;
}
@Component({
selector: 'abp-provider-key-search',
templateUrl: './provider-key-search.component.html',
imports: [LookupSearchComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'abp-provider-key-search',
templateUrl: './provider-key-search.component.html',
imports: [LookupSearchComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProviderKeySearchComponent implements OnInit {
readonly state = inject(ResourcePermissionStateService);
private readonly service = inject(PermissionsService);
private readonly destroyRef = inject(DestroyRef);
readonly state = inject(ResourcePermissionStateService);
private readonly service = inject(PermissionsService);
private readonly destroyRef = inject(DestroyRef);
readonly resourceName = input.required<string>();
readonly resourceName = input.required<string>();
searchFn: (filter: string) => Observable<ProviderKeyLookupItem[]> = () => new Observable();
searchFn: (filter: string) => Observable<ProviderKeyLookupItem[]> = () => new Observable();
ngOnInit() {
this.searchFn = (filter: string) => this.loadProviderKeys(filter);
}
ngOnInit() {
this.searchFn = (filter: string) => this.loadProviderKeys(filter);
}
onItemSelected(item: ProviderKeyLookupItem) {
// State is already updated via displayValue and selectedValue bindings
// This handler can be used for additional side effects if needed
}
onItemSelected(item: ProviderKeyLookupItem) {
// State is already updated via displayValue and selectedValue bindings
// This handler can be used for additional side effects if needed
}
private loadProviderKeys(filter: string): Observable<ProviderKeyLookupItem[]> {
const providerName = this.state.selectedProviderName();
if (!providerName) {
return new Observable(subscriber => {
subscriber.next([]);
subscriber.complete();
});
}
return this.service.searchResourceProviderKey(
this.resourceName(),
providerName,
filter,
1
).pipe(
map(res => (res.keys || []).map(k => ({
key: k.providerKey || '',
displayName: k.providerDisplayName || k.providerKey || '',
providerKey: k.providerKey || '',
providerDisplayName: k.providerDisplayName || undefined,
}))),
takeUntilDestroyed(this.destroyRef)
);
private loadProviderKeys(filter: string): Observable<ProviderKeyLookupItem[]> {
const providerName = this.state.selectedProviderName();
if (!providerName) {
return new Observable(subscriber => {
subscriber.next([]);
subscriber.complete();
});
}
return this.service
.searchResourceProviderKey(this.resourceName(), providerName, filter, 1)
.pipe(
map(res =>
(res.keys || []).map(k => ({
key: k.providerKey || '',
displayName: k.providerDisplayName || k.providerKey || '',
providerKey: k.providerKey || '',
providerDisplayName: k.providerDisplayName || undefined,
})),
),
takeUntilDestroyed(this.destroyRef),
);
}
}

41
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-form/resource-permission-form.component.html

@ -1,28 +1,37 @@
@if (mode() === eResourcePermissionViewModes.Add) {
<div class="mb-3">
<div class="mb-3">
<label class="form-label fw-bold">
{{ 'AbpPermissionManagement::SelectProvider' | abpLocalization }}
{{ 'AbpPermissionManagement::SelectProvider' | abpLocalization }}
</label>
<div class="mb-2">
@for (provider of state.providers(); track provider.name; let i = $index) {
@for (provider of state.providers(); track provider.name; let i = $index) {
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" [id]="'provider-' + i" [value]="provider.name"
[checked]="state.selectedProviderName() === provider.name"
(change)="state.onProviderChange(provider.name || '')" />
<label class="form-check-label" [for]="'provider-' + i">
{{ provider.displayName }}
</label>
<input
class="form-check-input"
type="radio"
[id]="'provider-' + i"
[value]="provider.name"
[checked]="state.selectedProviderName() === provider.name"
(change)="state.onProviderChange(provider.name || '')"
/>
<label class="form-check-label" [for]="'provider-' + i">
{{ provider.displayName }}
</label>
</div>
}
}
</div>
<abp-provider-key-search [resourceName]="resourceName()" />
</div>
</div>
<abp-permission-checkbox-list [permissions]="state.permissionDefinitions()" idPrefix="add" />
<abp-permission-checkbox-list [permissions]="state.permissionDefinitions()" idPrefix="add" />
} @else {
<div class="mb-3" id="permissionList">
<div class="mb-3" id="permissionList">
<h4>{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}</h4>
<abp-permission-checkbox-list [permissions]="state.permissionsWithProvider()" idPrefix="edit" [showTitle]="false" />
</div>
}
<abp-permission-checkbox-list
[permissions]="state.permissionsWithProvider()"
idPrefix="edit"
[showTitle]="false"
/>
</div>
}

107
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html

@ -1,60 +1,67 @@
<abp-modal [(visible)]="visible" [busy]="state.modalBusy()" [options]="{ size: 'xl', scrollable: false }">
<ng-template #abpHeader>
<h5 class="modal-title">
@switch (state.viewMode()) {
@case (eResourcePermissionViewModes.Edit) {
{{ 'AbpPermissionManagement::UpdatePermission' | abpLocalization }}
}
@case (eResourcePermissionViewModes.Add) {
{{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }}
}
@default {
{{ 'AbpPermissionManagement::ResourcePermissions' | abpLocalization }}
@if (resourceDisplayName()) {
<abp-modal
[(visible)]="visible"
[busy]="state.modalBusy()"
[options]="{ size: 'xl', scrollable: false }"
>
<ng-template #abpHeader>
<h5 class="modal-title">
@switch (state.viewMode()) {
@case (eResourcePermissionViewModes.Edit) {
{{ 'AbpPermissionManagement::UpdatePermission' | abpLocalization }}
}
@case (eResourcePermissionViewModes.Add) {
{{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }}
}
@default {
{{ 'AbpPermissionManagement::ResourcePermissions' | abpLocalization }}
@if (resourceDisplayName()) {
- {{ resourceDisplayName() }}
}
}
}
</h5>
</ng-template>
}
}
}
</h5>
</ng-template>
<ng-template #abpBody>
@if (!state.hasResourcePermission() || !state.hasProviderKeyLookupService()) {
<div class="alert alert-warning" role="alert">
@if (!state.hasResourcePermission()) {
{{ 'AbpPermissionManagement::NoResourcePermissionFound' | abpLocalization }}
} @else {
{{ 'AbpPermissionManagement::NoResourceProviderKeyLookupServiceFound' | abpLocalization }}
}
</div>
<ng-template #abpBody>
@if (!state.hasResourcePermission() || !state.hasProviderKeyLookupService()) {
<div class="alert alert-warning" role="alert">
@if (!state.hasResourcePermission()) {
{{ 'AbpPermissionManagement::NoResourcePermissionFound' | abpLocalization }}
} @else {
@switch (state.viewMode()) {
{{ 'AbpPermissionManagement::NoResourceProviderKeyLookupServiceFound' | abpLocalization }}
}
</div>
} @else {
@switch (state.viewMode()) {
@case (eResourcePermissionViewModes.List) {
<abp-resource-permission-list (addClicked)="onAddClicked()" (editClicked)="onEditClicked($event)"
(deleteClicked)="onDeleteClicked($event)" />
<abp-resource-permission-list
(addClicked)="onAddClicked()"
(editClicked)="onEditClicked($event)"
(deleteClicked)="onDeleteClicked($event)"
/>
}
@case (eResourcePermissionViewModes.Add) {
<abp-resource-permission-form mode="add" [resourceName]="resourceName()" />
<abp-resource-permission-form mode="add" [resourceName]="resourceName()" />
}
@case (eResourcePermissionViewModes.Edit) {
<abp-resource-permission-form mode="edit" [resourceName]="resourceName()" />
}
}
<abp-resource-permission-form mode="edit" [resourceName]="resourceName()" />
}
</ng-template>
}
}
</ng-template>
<ng-template #abpFooter>
@if (state.isListMode()) {
<button type="button" class="btn btn-outline-primary" abpClose>
{{ 'AbpUi::Close' | abpLocalization }}
</button>
} @else {
<button type="button" class="btn btn-outline-secondary" (click)="state.goToListMode()">
{{ 'AbpUi::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" (click)="savePermission()">
{{ 'AbpUi::Save' | abpLocalization }}
</abp-button>
}
</ng-template>
</abp-modal>
<ng-template #abpFooter>
@if (state.isListMode()) {
<button type="button" class="btn btn-outline-primary" abpClose>
{{ 'AbpUi::Close' | abpLocalization }}
</button>
} @else {
<button type="button" class="btn btn-outline-secondary" (click)="state.goToListMode()">
{{ 'AbpUi::Cancel' | abpLocalization }}
</button>
<abp-button iconClass="fa fa-check" (click)="savePermission()">
{{ 'AbpUi::Save' | abpLocalization }}
</abp-button>
}
</ng-template>
</abp-modal>

385
npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts

@ -1,26 +1,17 @@
import { ListService, LocalizationPipe } from '@abp/ng.core';
import {
ButtonComponent,
Confirmation,
ConfirmationService,
ModalCloseDirective,
ModalComponent,
ToasterService,
ButtonComponent,
Confirmation,
ConfirmationService,
ModalCloseDirective,
ModalComponent,
ToasterService,
} from '@abp/ng.theme.shared';
import {
PermissionsService,
ResourcePermissionGrantInfoDto,
PermissionsService,
ResourcePermissionGrantInfoDto,
} from '@abp/ng.permission-management/proxy';
import {
Component,
inject,
input,
model,
OnInit,
effect,
untracked,
signal,
} from '@angular/core';
import { Component, inject, input, model, OnInit, effect, untracked, signal } from '@angular/core';
import { finalize, switchMap, of } from 'rxjs';
import { ResourcePermissionStateService } from '../../services/resource-permission-state.service';
import { ResourcePermissionListComponent } from './resource-permission-list/resource-permission-list.component';
@ -31,187 +22,195 @@ import { eResourcePermissionViewModes } from '../../enums/view-modes';
const DEFAULT_MAX_RESULT_COUNT = 10;
@Component({
selector: 'abp-resource-permission-management',
templateUrl: './resource-permission-management.component.html',
exportAs: 'abpResourcePermissionManagement',
providers: [ResourcePermissionStateService, ListService],
imports: [
ModalComponent,
LocalizationPipe,
ButtonComponent,
ModalCloseDirective,
ResourcePermissionListComponent,
ResourcePermissionFormComponent,
],
selector: 'abp-resource-permission-management',
templateUrl: './resource-permission-management.component.html',
exportAs: 'abpResourcePermissionManagement',
providers: [ResourcePermissionStateService, ListService],
imports: [
ModalComponent,
LocalizationPipe,
ButtonComponent,
ModalCloseDirective,
ResourcePermissionListComponent,
ResourcePermissionFormComponent,
],
})
export class ResourcePermissionManagementComponent implements OnInit {
readonly eResourcePermissionViewModes = eResourcePermissionViewModes;
protected readonly service = inject(PermissionsService);
protected readonly toasterService = inject(ToasterService);
protected readonly confirmationService = inject(ConfirmationService);
protected readonly state = inject(ResourcePermissionStateService);
private readonly list = inject(ListService);
readonly resourceName = input.required<string>();
readonly resourceKey = input.required<string>();
readonly resourceDisplayName = input<string>();
readonly visible = model<boolean>(false);
private readonly previousVisible = signal(false);
constructor() {
effect(() => {
const resourceName = this.resourceName();
const resourceKey = this.resourceKey();
const resourceDisplayName = this.resourceDisplayName();
untracked(() => {
this.state.resourceName.set(resourceName);
this.state.resourceKey.set(resourceKey);
this.state.resourceDisplayName.set(resourceDisplayName);
});
});
effect(() => {
const isVisible = this.visible();
const wasVisible = this.previousVisible();
if (isVisible && !wasVisible) {
this.openModal();
} else if (!isVisible && wasVisible) {
this.state.reset();
}
untracked(() => this.previousVisible.set(isVisible));
});
}
ngOnInit() {
this.list.maxResultCount = DEFAULT_MAX_RESULT_COUNT;
this.list.hookToQuery(query => {
const allData = this.state.allResourcePermissions();
const skipCount = query.skipCount || 0;
const maxResultCount = query.maxResultCount || DEFAULT_MAX_RESULT_COUNT;
const paginatedData = allData.slice(skipCount, skipCount + maxResultCount);
return of({
items: paginatedData,
totalCount: allData.length
});
}).subscribe(result => {
this.state.resourcePermissions.set(result.items);
this.state.totalCount.set(result.totalCount);
readonly eResourcePermissionViewModes = eResourcePermissionViewModes;
protected readonly service = inject(PermissionsService);
protected readonly toasterService = inject(ToasterService);
protected readonly confirmationService = inject(ConfirmationService);
protected readonly state = inject(ResourcePermissionStateService);
private readonly list = inject(ListService);
readonly resourceName = input.required<string>();
readonly resourceKey = input.required<string>();
readonly resourceDisplayName = input<string>();
readonly visible = model<boolean>(false);
private readonly previousVisible = signal(false);
constructor() {
effect(() => {
const resourceName = this.resourceName();
const resourceKey = this.resourceKey();
const resourceDisplayName = this.resourceDisplayName();
untracked(() => {
this.state.resourceName.set(resourceName);
this.state.resourceKey.set(resourceKey);
this.state.resourceDisplayName.set(resourceDisplayName);
});
});
effect(() => {
const isVisible = this.visible();
const wasVisible = this.previousVisible();
if (isVisible && !wasVisible) {
this.openModal();
} else if (!isVisible && wasVisible) {
this.state.reset();
}
untracked(() => this.previousVisible.set(isVisible));
});
}
ngOnInit() {
this.list.maxResultCount = DEFAULT_MAX_RESULT_COUNT;
this.list
.hookToQuery(query => {
const allData = this.state.allResourcePermissions();
const skipCount = query.skipCount || 0;
const maxResultCount = query.maxResultCount || DEFAULT_MAX_RESULT_COUNT;
const paginatedData = allData.slice(skipCount, skipCount + maxResultCount);
return of({
items: paginatedData,
totalCount: allData.length,
});
}
openModal() {
this.state.modalBusy.set(true);
this.service.getResource(this.resourceName(), this.resourceKey()).pipe(
switchMap(permRes => {
this.state.setResourceData(permRes.permissions || []);
this.list.get();
return this.service.getResourceProviderKeyLookupServices(this.resourceName());
}),
switchMap(providerRes => {
this.state.setProviders(providerRes.providers || []);
return this.service.getResourceDefinitions(this.resourceName());
}),
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: defRes => {
this.state.setDefinitions(defRes.permissions || []);
},
error: () => {
this.toasterService.error('AbpPermissionManagement::ErrorLoadingPermissions');
}
});
}
onAddClicked() {
this.state.goToAddMode();
}
onEditClicked(grant: ResourcePermissionGrantInfoDto) {
this.state.prepareEditMode(grant);
this.state.modalBusy.set(true);
this.service.getResourceByProvider(
this.resourceName(),
this.resourceKey(),
grant.providerName || '',
grant.providerKey || ''
).pipe(
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: res => {
this.state.setEditModePermissions(res.permissions || []);
}
});
}
onDeleteClicked(grant: ResourcePermissionGrantInfoDto) {
this.confirmationService
.warn(
'AbpPermissionManagement::PermissionDeletionConfirmationMessage',
'AbpPermissionManagement::AreYouSure',
{
messageLocalizationParams: [grant.providerKey || ''],
}
})
.subscribe(result => {
this.state.resourcePermissions.set(result.items);
this.state.totalCount.set(result.totalCount);
});
}
openModal() {
this.state.modalBusy.set(true);
this.service
.getResource(this.resourceName(), this.resourceKey())
.pipe(
switchMap(permRes => {
this.state.setResourceData(permRes.permissions || []);
this.list.get();
return this.service.getResourceProviderKeyLookupServices(this.resourceName());
}),
switchMap(providerRes => {
this.state.setProviders(providerRes.providers || []);
return this.service.getResourceDefinitions(this.resourceName());
}),
finalize(() => this.state.modalBusy.set(false)),
)
.subscribe({
next: defRes => {
this.state.setDefinitions(defRes.permissions || []);
},
error: () => {
this.toasterService.error('AbpPermissionManagement::ErrorLoadingPermissions');
},
});
}
onAddClicked() {
this.state.goToAddMode();
}
onEditClicked(grant: ResourcePermissionGrantInfoDto) {
this.state.prepareEditMode(grant);
this.state.modalBusy.set(true);
this.service
.getResourceByProvider(
this.resourceName(),
this.resourceKey(),
grant.providerName || '',
grant.providerKey || '',
)
.pipe(finalize(() => this.state.modalBusy.set(false)))
.subscribe({
next: res => {
this.state.setEditModePermissions(res.permissions || []);
},
});
}
onDeleteClicked(grant: ResourcePermissionGrantInfoDto) {
this.confirmationService
.warn(
'AbpPermissionManagement::ResourcePermissionDeletionConfirmationMessage',
'AbpPermissionManagement::AreYouSure',
{
messageLocalizationParams: [grant.providerKey || ''],
},
)
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.state.modalBusy.set(true);
this.service
.deleteResource(
this.resourceName(),
this.resourceKey(),
grant.providerName || '',
grant.providerKey || '',
)
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.state.modalBusy.set(true);
this.service.deleteResource(
this.resourceName(),
this.resourceKey(),
grant.providerName || '',
grant.providerKey || ''
).pipe(
switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())),
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: res => {
this.state.setResourceData(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::SuccessfullyDeleted');
}
});
}
.pipe(
switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())),
finalize(() => this.state.modalBusy.set(false)),
)
.subscribe({
next: res => {
this.state.setResourceData(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::DeletedSuccessfully');
},
});
}
savePermission() {
const isEdit = this.state.isEditMode();
const providerName = isEdit ? this.state.editProviderName() : this.state.selectedProviderName();
const providerKey = isEdit ? this.state.editProviderKey() : this.state.selectedProviderKey();
if (!isEdit && !this.state.canSave()) {
this.toasterService.warn('AbpPermissionManagement::PleaseSelectProviderAndPermissions');
return;
}
});
}
this.state.modalBusy.set(true);
this.service.updateResource(
this.resourceName(),
this.resourceKey(),
{
providerName,
providerKey,
permissions: this.state.selectedPermissions()
}
).pipe(
switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())),
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: res => {
this.state.setResourceData(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::SavedSuccessfully');
this.state.goToListMode();
}
});
savePermission() {
const isEdit = this.state.isEditMode();
const providerName = isEdit ? this.state.editProviderName() : this.state.selectedProviderName();
const providerKey = isEdit ? this.state.editProviderKey() : this.state.selectedProviderKey();
if (!isEdit && !this.state.canSave()) {
this.toasterService.warn('AbpPermissionManagement::PleaseSelectProviderAndPermissions');
return;
}
this.state.modalBusy.set(true);
this.service
.updateResource(this.resourceName(), this.resourceKey(), {
providerName,
providerKey,
permissions: this.state.selectedPermissions(),
})
.pipe(
switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())),
finalize(() => this.state.modalBusy.set(false)),
)
.subscribe({
next: res => {
this.state.setResourceData(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::SavedSuccessfully');
this.state.goToListMode();
},
});
}
}

Loading…
Cancel
Save