Browse Source

Merge pull request #24525 from abpframework/auto-merge/rel-10-1/4233

Merge branch dev with rel-10.1
pull/24527/head
Ma Liming 9 months ago
committed by GitHub
parent
commit
e67d6bafab
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"> <div class="abp-lookup-container position-relative">
@if (label()) { @if (label()) {
<label class="form-label">{{ label() | abpLocalization }}</label> <label class="form-label">{{ label() | abpLocalization }}</label>
} }
<div class="input-group"> <div class="input-group">
<input type="text" class="form-control" [placeholder]="placeholder() | abpLocalization" [ngModel]="displayValue()" <input
(ngModelChange)="onSearchInput($event)" (focus)="onSearchFocus()" (blur)="onSearchBlur($event)" type="text"
[disabled]="disabled()" /> class="form-control"
[placeholder]="placeholder() | abpLocalization"
[ngModel]="displayValue()"
(ngModelChange)="onSearchInput($event)"
(focus)="onSearchFocus()"
(blur)="onSearchBlur($event)"
[disabled]="disabled()"
/>
@if (displayValue() && !disabled()) { @if (displayValue() && !disabled()) {
<button type="button" class="btn btn-outline-secondary" (mousedown)="clearSelection()" tabindex="-1"> <button
<i class="fa fa-times"></i> type="button"
</button> class="btn btn-outline-secondary"
(mousedown)="clearSelection()"
tabindex="-1"
>
<i class="fa fa-times"></i>
</button>
} }
</div> </div>
@if (showDropdown() && !disabled()) { @if (showDropdown() && !disabled()) {
<div class="abp-lookup-dropdown list-group position-absolute w-100 shadow"> <div class="abp-lookup-dropdown list-group position-absolute w-100">
@if (isLoading()) { @if (isLoading()) {
<div class="list-group-item text-center py-3"> <div class="list-group-item text-center py-3">
<i class="fa fa-spinner fa-spin me-2"></i> <i class="fa fa-spinner fa-spin me-2"></i>
{{ 'AbpUi::Loading' | abpLocalization }} {{ 'AbpUi::Loading' | abpLocalization }}
</div> </div>
} @else if (searchResults().length > 0) { } @else if (searchResults().length > 0) {
@for (item of searchResults(); track item.key) { @for (item of searchResults(); track item.key) {
<button type="button" class="list-group-item list-group-item-action" (mousedown)="selectItem(item)"> <button
@if (itemTemplate()) { type="button"
<ng-container *ngTemplateOutlet="itemTemplate()!; context: { $implicit: item }" /> class="list-group-item list-group-item-action"
} @else { (mousedown)="selectItem(item)"
{{ getDisplayValue(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> </div>

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

@ -1,5 +1,8 @@
.abp-lookup-dropdown { .abp-lookup-dropdown {
z-index: 1050; z-index: 1060;
max-height: 200px; max-height: 200px;
overflow-y: auto; 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 { import {
Component, Component,
input, input,
output, output,
model, model,
signal, signal,
OnInit, OnInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
TemplateRef, TemplateRef,
contentChild, contentChild,
DestroyRef, DestroyRef,
inject, inject,
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
@ -18,119 +18,123 @@ import { LocalizationPipe } from '@abp/ng.core';
import { Subject, Observable, debounceTime, distinctUntilChanged, of, finalize } from 'rxjs'; import { Subject, Observable, debounceTime, distinctUntilChanged, of, finalize } from 'rxjs';
export interface LookupItem { export interface LookupItem {
key: string; key: string;
displayName: string; displayName: string;
[key: string]: unknown; [key: string]: unknown;
} }
export type LookupSearchFn<T = LookupItem> = (filter: string) => Observable<T[]>; export type LookupSearchFn<T = LookupItem> = (filter: string) => Observable<T[]>;
@Component({ @Component({
selector: 'abp-lookup-search', selector: 'abp-lookup-search',
templateUrl: './lookup-search.component.html', templateUrl: './lookup-search.component.html',
styleUrl: './lookup-search.component.scss', styleUrl: './lookup-search.component.scss',
imports: [CommonModule, FormsModule, LocalizationPipe], imports: [CommonModule, FormsModule, LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class LookupSearchComponent<T extends LookupItem = LookupItem> implements OnInit { export class LookupSearchComponent<T extends LookupItem = LookupItem> implements OnInit {
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
readonly label = input<string>(); readonly label = input<string>();
readonly placeholder = input<string>(''); readonly placeholder = input<string>('');
readonly debounceTime = input<number>(300); readonly debounceTime = input<number>(300);
readonly minSearchLength = input<number>(0); readonly minSearchLength = input<number>(0);
readonly displayKey = input<keyof T>('displayName' as keyof T); readonly displayKey = input<keyof T>('displayName' as keyof T);
readonly valueKey = input<keyof T>('key' as keyof T); readonly valueKey = input<keyof T>('key' as keyof T);
readonly disabled = input<boolean>(false); readonly disabled = input<boolean>(false);
readonly searchFn = input<LookupSearchFn<T>>(() => of([])); readonly searchFn = input<LookupSearchFn<T>>(() => of([]));
readonly selectedValue = model<string>(''); readonly selectedValue = model<string>('');
readonly displayValue = model<string>(''); readonly displayValue = model<string>('');
readonly itemSelected = output<T>(); readonly itemSelected = output<T>();
readonly searchChanged = output<string>(); readonly searchChanged = output<string>();
readonly itemTemplate = contentChild<TemplateRef<{ $implicit: T }>>('itemTemplate'); readonly itemTemplate = contentChild<TemplateRef<{ $implicit: T }>>('itemTemplate');
readonly noResultsTemplate = contentChild<TemplateRef<void>>('noResultsTemplate'); readonly noResultsTemplate = contentChild<TemplateRef<void>>('noResultsTemplate');
readonly searchResults = signal<T[]>([]); readonly searchResults = signal<T[]>([]);
readonly showDropdown = signal(false); readonly showDropdown = signal(true);
readonly isLoading = signal(false); readonly isLoading = signal(false);
private readonly searchSubject = new Subject<string>(); private readonly searchSubject = new Subject<string>();
ngOnInit() { ngOnInit() {
this.searchSubject.pipe( this.searchSubject
debounceTime(this.debounceTime()), .pipe(
distinctUntilChanged(), debounceTime(this.debounceTime()),
takeUntilDestroyed(this.destroyRef) distinctUntilChanged(),
).subscribe(filter => { takeUntilDestroyed(this.destroyRef),
this.performSearch(filter); )
}); .subscribe(filter => {
} this.performSearch(filter);
});
onSearchInput(filter: string) { }
this.displayValue.set(filter);
this.showDropdown.set(true); onSearchInput(filter: string) {
this.searchChanged.emit(filter); this.displayValue.set(filter);
this.showDropdown.set(true);
if (filter.length >= this.minSearchLength()) { this.searchChanged.emit(filter);
this.searchSubject.next(filter);
} else { if (filter.length >= this.minSearchLength()) {
this.searchResults.set([]); 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 performSearch(filter: string) { onSearchFocus() {
this.isLoading.set(true); this.showDropdown.set(true);
const currentFilter = this.displayValue() || '';
this.searchFn()(filter).pipe( if (currentFilter.length >= this.minSearchLength()) {
takeUntilDestroyed(this.destroyRef), this.performSearch(currentFilter);
finalize(() => this.isLoading.set(false))
).subscribe({
next: results => {
this.searchResults.set(results);
},
error: () => {
this.searchResults.set([]);
}
});
} }
}
getDisplayValue(item: T): string { onSearchBlur(event: FocusEvent) {
return String(item[this.displayKey()] ?? item[this.valueKey()] ?? ''); 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"> <div class="mb-3">
@if (showTitle()) { @if (showTitle()) {
<h5>{{ title() | abpLocalization }}</h5> <h5>{{ title() | abpLocalization }}</h5>
} }
<div class="form-check form-switch mb-2"> <div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="grantAll-{{ idPrefix() }}" <input
[checked]="state.allPermissionsSelected()" class="form-check-input"
(change)="state.toggleAllPermissions(!state.allPermissionsSelected())" /> type="checkbox"
<label class="form-check-label" for="grantAll-{{ idPrefix() }}"> id="grantAll-{{ idPrefix() }}"
{{ 'AbpPermissionManagement::GrantAllResourcePermissions' | abpLocalization }} [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> </label>
</div> </div>
<div class="abp-permission-list-container border rounded p-3"> }
@for (perm of permissions(); track perm.name) { </div>
<div class="form-check"> </div>
<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>

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" <abp-lookup-search
placeholder="AbpPermissionManagement::SearchProviderKey" [searchFn]="searchFn" [displayValue]="state.searchFilter()" label="AbpPermissionManagement::SearchProviderKey"
(displayValueChange)="state.searchFilter.set($event)" [selectedValue]="state.selectedProviderKey()" placeholder="AbpPermissionManagement::SearchProviderKey"
(selectedValueChange)="state.selectedProviderKey.set($event)" (itemSelected)="onItemSelected($event)" /> [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 { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { PermissionsService } from '@abp/ng.permission-management/proxy'; import { PermissionsService } from '@abp/ng.permission-management/proxy';
import { LookupSearchComponent, LookupItem } from '@abp/ng.components/lookup'; 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'; import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
interface ProviderKeyLookupItem extends LookupItem { interface ProviderKeyLookupItem extends LookupItem {
providerKey: string; providerKey: string;
providerDisplayName?: string; providerDisplayName?: string;
} }
@Component({ @Component({
selector: 'abp-provider-key-search', selector: 'abp-provider-key-search',
templateUrl: './provider-key-search.component.html', templateUrl: './provider-key-search.component.html',
imports: [LookupSearchComponent], imports: [LookupSearchComponent],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class ProviderKeySearchComponent implements OnInit { export class ProviderKeySearchComponent implements OnInit {
readonly state = inject(ResourcePermissionStateService); readonly state = inject(ResourcePermissionStateService);
private readonly service = inject(PermissionsService); private readonly service = inject(PermissionsService);
private readonly destroyRef = inject(DestroyRef); 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() { ngOnInit() {
this.searchFn = (filter: string) => this.loadProviderKeys(filter); this.searchFn = (filter: string) => this.loadProviderKeys(filter);
} }
onItemSelected(item: ProviderKeyLookupItem) { onItemSelected(item: ProviderKeyLookupItem) {
// State is already updated via displayValue and selectedValue bindings // State is already updated via displayValue and selectedValue bindings
// This handler can be used for additional side effects if needed // This handler can be used for additional side effects if needed
} }
private loadProviderKeys(filter: string): Observable<ProviderKeyLookupItem[]> { private loadProviderKeys(filter: string): Observable<ProviderKeyLookupItem[]> {
const providerName = this.state.selectedProviderName(); const providerName = this.state.selectedProviderName();
if (!providerName) { if (!providerName) {
return new Observable(subscriber => { return new Observable(subscriber => {
subscriber.next([]); subscriber.next([]);
subscriber.complete(); 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)
);
} }
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) { @if (mode() === eResourcePermissionViewModes.Add) {
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-bold"> <label class="form-label fw-bold">
{{ 'AbpPermissionManagement::SelectProvider' | abpLocalization }} {{ 'AbpPermissionManagement::SelectProvider' | abpLocalization }}
</label> </label>
<div class="mb-2"> <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"> <div class="form-check form-check-inline">
<input class="form-check-input" type="radio" [id]="'provider-' + i" [value]="provider.name" <input
[checked]="state.selectedProviderName() === provider.name" class="form-check-input"
(change)="state.onProviderChange(provider.name || '')" /> type="radio"
<label class="form-check-label" [for]="'provider-' + i"> [id]="'provider-' + i"
{{ provider.displayName }} [value]="provider.name"
</label> [checked]="state.selectedProviderName() === provider.name"
(change)="state.onProviderChange(provider.name || '')"
/>
<label class="form-check-label" [for]="'provider-' + i">
{{ provider.displayName }}
</label>
</div> </div>
} }
</div> </div>
<abp-provider-key-search [resourceName]="resourceName()" /> <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 { } @else {
<div class="mb-3" id="permissionList"> <div class="mb-3" id="permissionList">
<h4>{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}</h4> <h4>{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}</h4>
<abp-permission-checkbox-list [permissions]="state.permissionsWithProvider()" idPrefix="edit" [showTitle]="false" /> <abp-permission-checkbox-list
</div> [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 }"> <abp-modal
<ng-template #abpHeader> [(visible)]="visible"
<h5 class="modal-title"> [busy]="state.modalBusy()"
@switch (state.viewMode()) { [options]="{ size: 'xl', scrollable: false }"
@case (eResourcePermissionViewModes.Edit) { >
{{ 'AbpPermissionManagement::UpdatePermission' | abpLocalization }} <ng-template #abpHeader>
} <h5 class="modal-title">
@case (eResourcePermissionViewModes.Add) { @switch (state.viewMode()) {
{{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }} @case (eResourcePermissionViewModes.Edit) {
} {{ 'AbpPermissionManagement::UpdatePermission' | abpLocalization }}
@default { }
{{ 'AbpPermissionManagement::ResourcePermissions' | abpLocalization }} @case (eResourcePermissionViewModes.Add) {
@if (resourceDisplayName()) { {{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }}
}
@default {
{{ 'AbpPermissionManagement::ResourcePermissions' | abpLocalization }}
@if (resourceDisplayName()) {
- {{ resourceDisplayName() }} - {{ resourceDisplayName() }}
} }
} }
} }
</h5> </h5>
</ng-template> </ng-template>
<ng-template #abpBody> <ng-template #abpBody>
@if (!state.hasResourcePermission() || !state.hasProviderKeyLookupService()) { @if (!state.hasResourcePermission() || !state.hasProviderKeyLookupService()) {
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
@if (!state.hasResourcePermission()) { @if (!state.hasResourcePermission()) {
{{ 'AbpPermissionManagement::NoResourcePermissionFound' | abpLocalization }} {{ 'AbpPermissionManagement::NoResourcePermissionFound' | abpLocalization }}
} @else {
{{ 'AbpPermissionManagement::NoResourceProviderKeyLookupServiceFound' | abpLocalization }}
}
</div>
} @else { } @else {
@switch (state.viewMode()) { {{ 'AbpPermissionManagement::NoResourceProviderKeyLookupServiceFound' | abpLocalization }}
}
</div>
} @else {
@switch (state.viewMode()) {
@case (eResourcePermissionViewModes.List) { @case (eResourcePermissionViewModes.List) {
<abp-resource-permission-list (addClicked)="onAddClicked()" (editClicked)="onEditClicked($event)" <abp-resource-permission-list
(deleteClicked)="onDeleteClicked($event)" /> (addClicked)="onAddClicked()"
(editClicked)="onEditClicked($event)"
(deleteClicked)="onDeleteClicked($event)"
/>
} }
@case (eResourcePermissionViewModes.Add) { @case (eResourcePermissionViewModes.Add) {
<abp-resource-permission-form mode="add" [resourceName]="resourceName()" /> <abp-resource-permission-form mode="add" [resourceName]="resourceName()" />
} }
@case (eResourcePermissionViewModes.Edit) { @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> <ng-template #abpFooter>
@if (state.isListMode()) { @if (state.isListMode()) {
<button type="button" class="btn btn-outline-primary" abpClose> <button type="button" class="btn btn-outline-primary" abpClose>
{{ 'AbpUi::Close' | abpLocalization }} {{ 'AbpUi::Close' | abpLocalization }}
</button> </button>
} @else { } @else {
<button type="button" class="btn btn-outline-secondary" (click)="state.goToListMode()"> <button type="button" class="btn btn-outline-secondary" (click)="state.goToListMode()">
{{ 'AbpUi::Cancel' | abpLocalization }} {{ 'AbpUi::Cancel' | abpLocalization }}
</button> </button>
<abp-button iconClass="fa fa-check" (click)="savePermission()"> <abp-button iconClass="fa fa-check" (click)="savePermission()">
{{ 'AbpUi::Save' | abpLocalization }} {{ 'AbpUi::Save' | abpLocalization }}
</abp-button> </abp-button>
} }
</ng-template> </ng-template>
</abp-modal> </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 { ListService, LocalizationPipe } from '@abp/ng.core';
import { import {
ButtonComponent, ButtonComponent,
Confirmation, Confirmation,
ConfirmationService, ConfirmationService,
ModalCloseDirective, ModalCloseDirective,
ModalComponent, ModalComponent,
ToasterService, ToasterService,
} from '@abp/ng.theme.shared'; } from '@abp/ng.theme.shared';
import { import {
PermissionsService, PermissionsService,
ResourcePermissionGrantInfoDto, ResourcePermissionGrantInfoDto,
} from '@abp/ng.permission-management/proxy'; } from '@abp/ng.permission-management/proxy';
import { import { Component, inject, input, model, OnInit, effect, untracked, signal } from '@angular/core';
Component,
inject,
input,
model,
OnInit,
effect,
untracked,
signal,
} from '@angular/core';
import { finalize, switchMap, of } from 'rxjs'; import { finalize, switchMap, of } from 'rxjs';
import { ResourcePermissionStateService } from '../../services/resource-permission-state.service'; import { ResourcePermissionStateService } from '../../services/resource-permission-state.service';
import { ResourcePermissionListComponent } from './resource-permission-list/resource-permission-list.component'; 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; const DEFAULT_MAX_RESULT_COUNT = 10;
@Component({ @Component({
selector: 'abp-resource-permission-management', selector: 'abp-resource-permission-management',
templateUrl: './resource-permission-management.component.html', templateUrl: './resource-permission-management.component.html',
exportAs: 'abpResourcePermissionManagement', exportAs: 'abpResourcePermissionManagement',
providers: [ResourcePermissionStateService, ListService], providers: [ResourcePermissionStateService, ListService],
imports: [ imports: [
ModalComponent, ModalComponent,
LocalizationPipe, LocalizationPipe,
ButtonComponent, ButtonComponent,
ModalCloseDirective, ModalCloseDirective,
ResourcePermissionListComponent, ResourcePermissionListComponent,
ResourcePermissionFormComponent, ResourcePermissionFormComponent,
], ],
}) })
export class ResourcePermissionManagementComponent implements OnInit { export class ResourcePermissionManagementComponent implements OnInit {
readonly eResourcePermissionViewModes = eResourcePermissionViewModes; readonly eResourcePermissionViewModes = eResourcePermissionViewModes;
protected readonly service = inject(PermissionsService); protected readonly service = inject(PermissionsService);
protected readonly toasterService = inject(ToasterService); protected readonly toasterService = inject(ToasterService);
protected readonly confirmationService = inject(ConfirmationService); protected readonly confirmationService = inject(ConfirmationService);
protected readonly state = inject(ResourcePermissionStateService); protected readonly state = inject(ResourcePermissionStateService);
private readonly list = inject(ListService); private readonly list = inject(ListService);
readonly resourceName = input.required<string>(); readonly resourceName = input.required<string>();
readonly resourceKey = input.required<string>(); readonly resourceKey = input.required<string>();
readonly resourceDisplayName = input<string>(); readonly resourceDisplayName = input<string>();
readonly visible = model<boolean>(false); readonly visible = model<boolean>(false);
private readonly previousVisible = signal(false); private readonly previousVisible = signal(false);
constructor() { constructor() {
effect(() => { effect(() => {
const resourceName = this.resourceName(); const resourceName = this.resourceName();
const resourceKey = this.resourceKey(); const resourceKey = this.resourceKey();
const resourceDisplayName = this.resourceDisplayName(); const resourceDisplayName = this.resourceDisplayName();
untracked(() => { untracked(() => {
this.state.resourceName.set(resourceName); this.state.resourceName.set(resourceName);
this.state.resourceKey.set(resourceKey); this.state.resourceKey.set(resourceKey);
this.state.resourceDisplayName.set(resourceDisplayName); this.state.resourceDisplayName.set(resourceDisplayName);
}); });
}); });
effect(() => { effect(() => {
const isVisible = this.visible(); const isVisible = this.visible();
const wasVisible = this.previousVisible(); const wasVisible = this.previousVisible();
if (isVisible && !wasVisible) { if (isVisible && !wasVisible) {
this.openModal(); this.openModal();
} else if (!isVisible && wasVisible) { } else if (!isVisible && wasVisible) {
this.state.reset(); this.state.reset();
} }
untracked(() => this.previousVisible.set(isVisible)); untracked(() => this.previousVisible.set(isVisible));
}); });
} }
ngOnInit() { ngOnInit() {
this.list.maxResultCount = DEFAULT_MAX_RESULT_COUNT; this.list.maxResultCount = DEFAULT_MAX_RESULT_COUNT;
this.list.hookToQuery(query => { this.list
const allData = this.state.allResourcePermissions(); .hookToQuery(query => {
const skipCount = query.skipCount || 0; const allData = this.state.allResourcePermissions();
const maxResultCount = query.maxResultCount || DEFAULT_MAX_RESULT_COUNT; const skipCount = query.skipCount || 0;
const maxResultCount = query.maxResultCount || DEFAULT_MAX_RESULT_COUNT;
const paginatedData = allData.slice(skipCount, skipCount + maxResultCount);
const paginatedData = allData.slice(skipCount, skipCount + maxResultCount);
return of({
items: paginatedData, return of({
totalCount: allData.length items: paginatedData,
}); totalCount: allData.length,
}).subscribe(result => {
this.state.resourcePermissions.set(result.items);
this.state.totalCount.set(result.totalCount);
}); });
} })
.subscribe(result => {
openModal() { this.state.resourcePermissions.set(result.items);
this.state.modalBusy.set(true); this.state.totalCount.set(result.totalCount);
});
this.service.getResource(this.resourceName(), this.resourceKey()).pipe( }
switchMap(permRes => {
this.state.setResourceData(permRes.permissions || []); openModal() {
this.list.get(); this.state.modalBusy.set(true);
return this.service.getResourceProviderKeyLookupServices(this.resourceName());
}), this.service
switchMap(providerRes => { .getResource(this.resourceName(), this.resourceKey())
this.state.setProviders(providerRes.providers || []); .pipe(
return this.service.getResourceDefinitions(this.resourceName()); switchMap(permRes => {
}), this.state.setResourceData(permRes.permissions || []);
finalize(() => this.state.modalBusy.set(false)) this.list.get();
).subscribe({ return this.service.getResourceProviderKeyLookupServices(this.resourceName());
next: defRes => { }),
this.state.setDefinitions(defRes.permissions || []); switchMap(providerRes => {
}, this.state.setProviders(providerRes.providers || []);
error: () => { return this.service.getResourceDefinitions(this.resourceName());
this.toasterService.error('AbpPermissionManagement::ErrorLoadingPermissions'); }),
} finalize(() => this.state.modalBusy.set(false)),
}); )
} .subscribe({
next: defRes => {
onAddClicked() { this.state.setDefinitions(defRes.permissions || []);
this.state.goToAddMode(); },
} error: () => {
this.toasterService.error('AbpPermissionManagement::ErrorLoadingPermissions');
onEditClicked(grant: ResourcePermissionGrantInfoDto) { },
this.state.prepareEditMode(grant); });
this.state.modalBusy.set(true); }
this.service.getResourceByProvider( onAddClicked() {
this.resourceName(), this.state.goToAddMode();
this.resourceKey(), }
grant.providerName || '',
grant.providerKey || '' onEditClicked(grant: ResourcePermissionGrantInfoDto) {
).pipe( this.state.prepareEditMode(grant);
finalize(() => this.state.modalBusy.set(false)) this.state.modalBusy.set(true);
).subscribe({
next: res => { this.service
this.state.setEditModePermissions(res.permissions || []); .getResourceByProvider(
} this.resourceName(),
}); this.resourceKey(),
} grant.providerName || '',
grant.providerKey || '',
onDeleteClicked(grant: ResourcePermissionGrantInfoDto) { )
this.confirmationService .pipe(finalize(() => this.state.modalBusy.set(false)))
.warn( .subscribe({
'AbpPermissionManagement::PermissionDeletionConfirmationMessage', next: res => {
'AbpPermissionManagement::AreYouSure', this.state.setEditModePermissions(res.permissions || []);
{ },
messageLocalizationParams: [grant.providerKey || ''], });
} }
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) => { .pipe(
if (status === Confirmation.Status.confirm) { switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())),
this.state.modalBusy.set(true); finalize(() => this.state.modalBusy.set(false)),
this.service.deleteResource( )
this.resourceName(), .subscribe({
this.resourceKey(), next: res => {
grant.providerName || '', this.state.setResourceData(res.permissions || []);
grant.providerKey || '' this.list.get();
).pipe( this.toasterService.success('AbpUi::DeletedSuccessfully');
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');
}
});
}
}); });
}
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); savePermission() {
this.service.updateResource( const isEdit = this.state.isEditMode();
this.resourceName(), const providerName = isEdit ? this.state.editProviderName() : this.state.selectedProviderName();
this.resourceKey(), const providerKey = isEdit ? this.state.editProviderKey() : this.state.selectedProviderKey();
{
providerName, if (!isEdit && !this.state.canSave()) {
providerKey, this.toasterService.warn('AbpPermissionManagement::PleaseSelectProviderAndPermissions');
permissions: this.state.selectedPermissions() return;
}
).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();
}
});
} }
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