Browse Source

Refactor resource permission management to use state service

Introduces ResourcePermissionStateService to centralize and manage state for resource permission management. Refactors resource-permission-management.component and related templates to use the new state service, and extracts form, list, and search UI into dedicated components for improved modularity and maintainability.
pull/24459/head
Fahri Gedik 7 months ago
parent
commit
3ad378f429
  1. 4
      npm/ng-packs/packages/permission-management/src/lib/components/index.ts
  2. 57
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/permission-checkbox-list/permission-checkbox-list.component.ts
  3. 105
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/provider-key-search/provider-key-search.component.ts
  4. 70
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-form/resource-permission-form.component.ts
  5. 75
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-list/resource-permission-list.component.ts
  6. 151
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.html
  7. 367
      npm/ng-packs/packages/permission-management/src/lib/components/resource-permission-management/resource-permission-management.component.ts
  8. 1
      npm/ng-packs/packages/permission-management/src/lib/services/index.ts
  9. 165
      npm/ng-packs/packages/permission-management/src/lib/services/resource-permission-state.service.ts

4
npm/ng-packs/packages/permission-management/src/lib/components/index.ts

@ -1,2 +1,6 @@
export * from './permission-management.component';
export * from './resource-permission-management/resource-permission-management.component';
export * from './resource-permission-management/provider-key-search/provider-key-search.component';
export * from './resource-permission-management/permission-checkbox-list/permission-checkbox-list.component';
export * from './resource-permission-management/resource-permission-list/resource-permission-list.component';
export * from './resource-permission-management/resource-permission-form/resource-permission-form.component';

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

@ -0,0 +1,57 @@
import { Component, input, inject, ChangeDetectionStrategy } from '@angular/core';
import { LocalizationPipe } from '@abp/ng.core';
import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
interface PermissionItem {
name?: string | null;
displayName?: string | null;
}
@Component({
selector: 'abp-permission-checkbox-list',
template: `
<div class="mb-3">
@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 }}
</label>
</div>
<div class="border rounded p-3" style="max-height: 300px; overflow-y: auto;">
@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>
`,
imports: [LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PermissionCheckboxListComponent {
readonly state = inject(ResourcePermissionStateService);
readonly permissions = input.required<PermissionItem[]>();
readonly idPrefix = input<string>('default');
readonly title = input<string>('AbpPermissionManagement::ResourcePermissionPermissions');
readonly showTitle = input<boolean>(true);
}

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

@ -0,0 +1,105 @@
import { Component, input, inject, output, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LocalizationPipe } from '@abp/ng.core';
import { PermissionsService, SearchProviderKeyInfo } from '@abp/ng.permission-management/proxy';
import { Subject, debounceTime, distinctUntilChanged, takeUntil } from 'rxjs';
import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
@Component({
selector: 'abp-provider-key-search',
template: `
<div class="position-relative">
<label class="form-label">{{ 'AbpPermissionManagement::SearchProviderKey' | abpLocalization }}</label>
<input
type="text"
class="form-control"
[placeholder]="'AbpPermissionManagement::SearchProviderKey' | abpLocalization"
[ngModel]="state.searchFilter()"
(ngModelChange)="onSearchInput($event)"
(focus)="onSearchFocus()"
(blur)="onSearchBlur($event)"
/>
@if (state.searchResults().length > 0 && state.showDropdown()) {
<div
class="list-group position-absolute w-100 shadow"
style="z-index: 1000; max-height: 200px; overflow-y: auto;"
>
@for (result of state.searchResults(); track result.providerKey) {
<button
type="button"
class="list-group-item list-group-item-action"
(mousedown)="selectProviderKey(result)"
>
{{ result.providerDisplayName || result.providerKey }}
</button>
}
</div>
}
</div>
`,
imports: [FormsModule, LocalizationPipe],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ProviderKeySearchComponent implements OnInit, OnDestroy {
readonly state = inject(ResourcePermissionStateService);
private readonly service = inject(PermissionsService);
readonly resourceName = input.required<string>();
readonly keySelected = output<SearchProviderKeyInfo>();
private readonly searchSubject = new Subject<string>();
private readonly destroy$ = new Subject<void>();
ngOnInit() {
this.searchSubject.pipe(
debounceTime(300),
distinctUntilChanged(),
takeUntil(this.destroy$)
).subscribe(filter => {
this.loadProviderKeys(filter);
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
onSearchInput(filter: string) {
this.state.searchFilter.set(filter);
this.state.showDropdown.set(true);
this.searchSubject.next(filter);
}
onSearchFocus() {
this.state.showDropdown.set(true);
this.loadProviderKeys(this.state.searchFilter() || '');
}
onSearchBlur(event: FocusEvent) {
const relatedTarget = event.relatedTarget as HTMLElement;
if (!relatedTarget?.closest('.list-group')) {
this.state.showDropdown.set(false);
}
}
selectProviderKey(key: SearchProviderKeyInfo) {
this.state.selectProviderKey(key);
this.keySelected.emit(key);
}
private loadProviderKeys(filter: string) {
const providerName = this.state.selectedProviderName();
if (!providerName) return;
this.service.searchResourceProviderKey(
this.resourceName(),
providerName,
filter,
1
).subscribe(res => {
this.state.searchResults.set(res.keys || []);
});
}
}

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

@ -0,0 +1,70 @@
import { Component, input, inject, output, ChangeDetectionStrategy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { LocalizationPipe } from '@abp/ng.core';
import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
import { ProviderKeySearchComponent } from '../provider-key-search/provider-key-search.component';
import { PermissionCheckboxListComponent } from '../permission-checkbox-list/permission-checkbox-list.component';
export type FormMode = 'add' | 'edit';
@Component({
selector: 'abp-resource-permission-form',
template: `
@if (mode() === 'add') {
<div class="mb-3">
<label class="form-label fw-bold">
{{ 'AbpPermissionManagement::SelectProvider' | abpLocalization }}
</label>
<div class="mb-2">
@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>
</div>
}
</div>
<abp-provider-key-search [resourceName]="resourceName()" />
</div>
<abp-permission-checkbox-list
[permissions]="state.permissionDefinitions()"
idPrefix="add"
/>
} @else {
<div class="mb-3" id="permissionList">
<h4>{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}</h4>
<abp-permission-checkbox-list
[permissions]="state.permissionsWithProvider()"
idPrefix="edit"
[showTitle]="false"
/>
</div>
}
`,
imports: [
FormsModule,
LocalizationPipe,
ProviderKeySearchComponent,
PermissionCheckboxListComponent,
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ResourcePermissionFormComponent {
readonly state = inject(ResourcePermissionStateService);
readonly mode = input.required<FormMode>();
readonly resourceName = input.required<string>();
readonly save = output<void>();
readonly cancel = output<void>();
}

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

@ -0,0 +1,75 @@
import { Component, inject, output, ChangeDetectionStrategy } from '@angular/core';
import { ListService, LocalizationPipe } from '@abp/ng.core';
import { ExtensibleTableComponent, EXTENSIONS_IDENTIFIER } from '@abp/ng.components/extensible';
import { ResourcePermissionGrantInfoDto } from '@abp/ng.permission-management/proxy';
import { ResourcePermissionStateService } from '../../../services/resource-permission-state.service';
import { ePermissionManagementComponents } from '../../../enums/components';
import { configureResourcePermissionExtensions } from '../../../services/extensions.service';
@Component({
selector: 'abp-resource-permission-list',
template: `
<div class="d-grid gap-2 mb-3 d-md-flex justify-content-md-end">
<button class="btn btn-sm btn-primary" type="button" (click)="addClicked.emit()">
<i class="fa fa-plus me-1"></i>
{{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }}
</button>
</div>
<ng-template #actionsTemplate let-row>
<div class="btn-group btn-group-sm" role="group">
<button
class="btn btn-outline-primary"
type="button"
(click)="editClicked.emit(row)"
[title]="'AbpUi::Edit' | abpLocalization"
>
<i class="fa fa-edit"></i>
</button>
<button
class="btn btn-outline-danger"
type="button"
(click)="deleteClicked.emit(row)"
[title]="'AbpUi::Delete' | abpLocalization"
>
<i class="fa fa-trash"></i>
</button>
</div>
</ng-template>
@if (state.resourcePermissions().length > 0) {
<abp-extensible-table
[data]="state.resourcePermissions()"
[recordsTotal]="state.totalCount()"
[list]="list"
[actionsTemplate]="actionsTemplate"
actionsText="AbpUi::Actions"
></abp-extensible-table>
} @else {
<div class="alert alert-info">
{{ 'AbpPermissionManagement::NoPermissionsAssigned' | abpLocalization }}
</div>
}
`,
providers: [
ListService,
{
provide: EXTENSIONS_IDENTIFIER,
useValue: ePermissionManagementComponents.ResourcePermissions,
},
],
imports: [LocalizationPipe, ExtensibleTableComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ResourcePermissionListComponent {
readonly state = inject(ResourcePermissionStateService);
readonly list = inject(ListService);
readonly addClicked = output<void>();
readonly editClicked = output<ResourcePermissionGrantInfoDto>();
readonly deleteClicked = output<ResourcePermissionGrantInfoDto>();
constructor() {
configureResourcePermissionExtensions();
}
}

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

@ -1,165 +1,60 @@
<abp-modal [(visible)]="visible" [busy]="modalBusy()" [options]="{ size: 'xl', scrollable: false }">
<abp-modal [(visible)]="visible" [busy]="state.modalBusy()" [options]="{ size: 'xl', scrollable: false }">
<ng-template #abpHeader>
<h5 class="modal-title">
@if (viewMode() === 'edit') {
@switch (state.viewMode()) {
@case ('edit') {
{{ 'AbpPermissionManagement::UpdatePermission' | abpLocalization }}
} @else if (viewMode() === 'add') {
}
@case ('add') {
{{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }}
} @else {
}
@default {
{{ 'AbpPermissionManagement::ResourcePermissions' | abpLocalization }}
@if (resourceDisplayName) {
- {{ resourceDisplayName }}
@if (resourceDisplayName()) {
- {{ resourceDisplayName() }}
}
}
}
</h5>
</ng-template>
<ng-template #abpBody>
@if (!hasResourcePermission() || !hasProviderKeyLookupService()) {
@if (!state.hasResourcePermission() || !state.hasProviderKeyLookupService()) {
<div class="alert alert-warning" role="alert">
@if (!hasResourcePermission()) {
@if (!state.hasResourcePermission()) {
{{ 'AbpPermissionManagement::NoResourcePermissionFound' | abpLocalization }}
} @else {
{{ 'AbpPermissionManagement::NoResourceProviderKeyLookupServiceFound' | abpLocalization }}
}
</div>
} @else {
<!-- LIST VIEW -->
@if (viewMode() === 'list') {
<div class="d-grid gap-2 mb-3 d-md-flex justify-content-md-end">
<button class="btn btn-sm btn-primary" type="button" (click)="goToAddMode()">
<i class="fa fa-plus me-1"></i>
{{ 'AbpPermissionManagement::AddResourcePermission' | abpLocalization }}
</button>
</div>
<ng-template #actionsTemplate let-row>
<div class="btn-group btn-group-sm" role="group">
<button class="btn btn-outline-primary" type="button" (click)="goToEditMode(row)"
[title]="'AbpUi::Edit' | abpLocalization">
<i class="fa fa-edit"></i>
</button>
<button class="btn btn-outline-danger" type="button" (click)="deletePermission(row)"
[title]="'AbpUi::Delete' | abpLocalization">
<i class="fa fa-trash"></i>
</button>
</div>
</ng-template>
@if (resourcePermissions().length > 0) {
<abp-extensible-table [data]="resourcePermissions()" [recordsTotal]="totalCount()" [list]="list"
[actionsTemplate]="actionsTemplate" actionsText="AbpUi::Actions"></abp-extensible-table>
} @else {
<div class="alert alert-info">
{{ 'AbpPermissionManagement::NoPermissionsAssigned' | abpLocalization }}
</div>
@switch (state.viewMode()) {
@case ('list') {
<abp-resource-permission-list (addClicked)="onAddClicked()" (editClicked)="onEditClicked($event)"
(deleteClicked)="onDeleteClicked($event)" />
}
@case ('add') {
<abp-resource-permission-form mode="add" [resourceName]="resourceName()" />
}
<!-- ADD VIEW -->
@if (viewMode() === 'add') {
<div class="mb-3">
<label class="form-label fw-bold">{{ 'AbpPermissionManagement::SelectProvider' | abpLocalization }}</label>
<div class="mb-2">
@for (provider of 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]="selectedProviderName() === provider.name"
(change)="onProviderChange(provider.name || '')" />
<label class="form-check-label" [for]="'provider-' + i">
{{ provider.displayName }}
</label>
</div>
}
</div>
<div class="position-relative mb-3">
<label class="form-label">{{ 'AbpPermissionManagement::SearchProviderKey' | abpLocalization }}</label>
<input type="text" class="form-control"
[placeholder]="'AbpPermissionManagement::SearchProviderKey' | abpLocalization"
[ngModel]="searchFilter()" (ngModelChange)="onSearchInput($event)" (focus)="onSearchFocus()"
(blur)="onSearchBlur()" />
@if (searchResults().length > 0 && showDropdown()) {
<div class="list-group position-absolute w-100 shadow"
style="z-index: 1000; max-height: 200px; overflow-y: auto;">
@for (result of searchResults(); track result.providerKey) {
<button type="button" class="list-group-item list-group-item-action"
(mousedown)="selectProviderKey(result)">
{{ result.providerDisplayName || result.providerKey }}
</button>
}
</div>
}
</div>
</div>
<div class="mb-3">
<h5>{{ 'AbpPermissionManagement::ResourcePermissionPermissions' | abpLocalization }}</h5>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="grantAllAdd" [checked]="allPermissionsSelected()"
(change)="toggleAllPermissions(!allPermissionsSelected())" />
<label class="form-check-label" for="grantAllAdd">
{{ 'AbpPermissionManagement::GrantAllResourcePermissions' | abpLocalization }}
</label>
</div>
<div class="border rounded p-3" style="max-height: 300px; overflow-y: auto;">
@for (perm of permissionDefinitions(); track perm.name) {
<div class="form-check">
<input class="form-check-input" type="checkbox" [id]="'perm-add-' + perm.name"
[checked]="isPermissionSelected(perm.name || '')"
(change)="togglePermission(perm.name || '')" />
<label class="form-check-label" [for]="'perm-add-' + perm.name">
{{ perm.displayName }}
</label>
</div>
}
</div>
</div>
@case ('edit') {
<abp-resource-permission-form mode="edit" [resourceName]="resourceName()" />
}
<!-- EDIT VIEW -->
@if (viewMode() === 'edit') {
<div class="mb-3" id="permissionList">
<h4>{{ 'AbpPermissionManagement::Permissions' | abpLocalization }}</h4>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="grantAllEdit" [checked]="allPermissionsSelected()"
(change)="toggleAllPermissions(!allPermissionsSelected())" />
<label class="form-check-label" for="grantAllEdit">
{{ 'AbpPermissionManagement::GrantAllResourcePermissions' | abpLocalization }}
</label>
</div>
@for (perm of permissionsWithProvider(); track perm.name) {
<div class="form-check">
<input class="form-check-input" type="checkbox" [id]="'perm-edit-' + perm.name"
[checked]="isPermissionSelected(perm.name || '')" (change)="togglePermission(perm.name || '')" />
<label class="form-check-label" [for]="'perm-edit-' + perm.name">
{{ perm.displayName }}
</label>
</div>
}
</div>
}
}
</ng-template>
<ng-template #abpFooter>
@if (viewMode() === 'list') {
@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)="goToListMode()">
<button type="button" class="btn btn-outline-secondary" (click)="state.goToListMode()">
{{ 'AbpUi::Cancel' | abpLocalization }}
</button>
@if (viewMode() === 'add') {
<abp-button iconClass="fa fa-check" (click)="saveAddPermission()">
<abp-button iconClass="fa fa-check" (click)="savePermission()">
{{ 'AbpUi::Save' | abpLocalization }}
</abp-button>
} @else {
<abp-button iconClass="fa fa-check" (click)="saveEditPermission()">
{{ 'AbpUi::Save' | abpLocalization }}
</abp-button>
}
}
</ng-template>
</abp-modal>

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

@ -10,116 +10,66 @@ import {
import {
PermissionsService,
ResourcePermissionGrantInfoDto,
ResourceProviderDto,
SearchProviderKeyInfo,
ResourcePermissionDefinitionDto,
ResourcePermissionWithProdiverGrantInfoDto,
} from '@abp/ng.permission-management/proxy';
import {
ExtensibleTableComponent,
EXTENSIONS_IDENTIFIER,
} from '@abp/ng.components/extensible';
import {
Component,
EventEmitter,
inject,
Input,
Output,
signal,
computed,
input,
model,
OnInit,
effect,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { finalize, switchMap, debounceTime, Subject, distinctUntilChanged, of } from 'rxjs';
import { ePermissionManagementComponents } from '../../enums/components';
import { configureResourcePermissionExtensions } from '../../services/extensions.service';
type ViewMode = 'list' | 'add' | 'edit';
import { finalize, switchMap, of } from 'rxjs';
import { ResourcePermissionStateService } from '../../services/resource-permission-state.service';
import { ResourcePermissionListComponent } from './resource-permission-list/resource-permission-list.component';
import { ResourcePermissionFormComponent } from './resource-permission-form/resource-permission-form.component';
@Component({
selector: 'abp-resource-permission-management',
templateUrl: './resource-permission-management.component.html',
exportAs: 'abpResourcePermissionManagement',
providers: [
ListService,
{
provide: EXTENSIONS_IDENTIFIER,
useValue: ePermissionManagementComponents.ResourcePermissions,
},
],
providers: [ResourcePermissionStateService, ListService],
imports: [
FormsModule,
ModalComponent,
LocalizationPipe,
ButtonComponent,
ModalCloseDirective,
ExtensibleTableComponent,
ResourcePermissionListComponent,
ResourcePermissionFormComponent,
],
})
export class ResourcePermissionManagementComponent implements OnInit {
protected readonly service = inject(PermissionsService);
protected readonly toasterService = inject(ToasterService);
protected readonly confirmationService = inject(ConfirmationService);
readonly list = inject(ListService);
@Input() resourceName!: string;
@Input() resourceKey!: string;
@Input() resourceDisplayName?: string;
protected _visible = false;
@Input()
get visible(): boolean {
return this._visible;
}
set visible(value: boolean) {
if (value === this._visible) return;
if (value) {
this.openModal();
} else {
this.resetState();
}
this._visible = value;
this.visibleChange.emit(value);
}
@Output() readonly visibleChange = new EventEmitter<boolean>();
protected readonly state = inject(ResourcePermissionStateService);
private readonly list = inject(ListService);
viewMode = signal<ViewMode>('list');
modalBusy = signal(false);
hasResourcePermission = signal(false);
hasProviderKeyLookupService = signal(false);
readonly resourceName = input.required<string>();
readonly resourceKey = input.required<string>();
readonly resourceDisplayName = input<string>();
allResourcePermissions = signal<ResourcePermissionGrantInfoDto[]>([]); // All data for client-side pagination
resourcePermissions = signal<ResourcePermissionGrantInfoDto[]>([]); // Paginated data for table
totalCount = signal(0);
providers = signal<ResourceProviderDto[]>([]);
permissionDefinitions = signal<ResourcePermissionDefinitionDto[]>([]);
searchResults = signal<SearchProviderKeyInfo[]>([]);
permissionsWithProvider = signal<ResourcePermissionWithProdiverGrantInfoDto[]>([]);
readonly visible = model<boolean>(false);
selectedProviderName = signal<string>('');
selectedProviderKey = signal<string>('');
searchFilter = signal('');
selectedPermissions = signal<string[]>([]);
editProviderName = signal<string>('');
editProviderKey = signal<string>('');
showDropdown = signal(false);
private searchSubject = new Subject<string>();
private previousVisible = false;
constructor() {
configureResourcePermissionExtensions();
// Sync input values to state
effect(() => {
this.state.resourceName.set(this.resourceName());
this.state.resourceKey.set(this.resourceKey());
this.state.resourceDisplayName.set(this.resourceDisplayName());
});
this.searchSubject.pipe(
debounceTime(300),
distinctUntilChanged()
).subscribe(filter => {
this.performSearch(filter);
// Handle visibility changes
effect(() => {
const isVisible = this.visible();
if (isVisible && !this.previousVisible) {
this.openModal();
} else if (!isVisible && this.previousVisible) {
this.state.reset();
}
this.previousVisible = isVisible;
});
}
@ -127,7 +77,7 @@ export class ResourcePermissionManagementComponent implements OnInit {
this.list.maxResultCount = 10;
this.list.hookToQuery(query => {
const allData = this.allResourcePermissions();
const allData = this.state.allResourcePermissions();
const skipCount = query.skipCount || 0;
const maxResultCount = query.maxResultCount || 10;
@ -138,34 +88,28 @@ export class ResourcePermissionManagementComponent implements OnInit {
totalCount: allData.length
});
}).subscribe(result => {
this.resourcePermissions.set(result.items);
this.totalCount.set(result.totalCount);
this.state.resourcePermissions.set(result.items);
this.state.totalCount.set(result.totalCount);
});
}
openModal() {
this.modalBusy.set(true);
this.state.modalBusy.set(true);
this.service.getResource(this.resourceName, this.resourceKey).pipe(
this.service.getResource(this.resourceName(), this.resourceKey()).pipe(
switchMap(permRes => {
this.allResourcePermissions.set(permRes.permissions || []);
this.totalCount.set(permRes.permissions?.length || 0);
this.state.setResourceData(permRes.permissions || []);
this.list.get();
return this.service.getResourceProviderKeyLookupServices(this.resourceName);
return this.service.getResourceProviderKeyLookupServices(this.resourceName());
}),
switchMap(providerRes => {
this.providers.set(providerRes.providers || []);
this.hasProviderKeyLookupService.set((providerRes.providers?.length || 0) > 0);
if (providerRes.providers?.length) {
this.selectedProviderName.set(providerRes.providers[0].name || '');
}
return this.service.getResourceDefinitions(this.resourceName);
this.state.setProviders(providerRes.providers || []);
return this.service.getResourceDefinitions(this.resourceName());
}),
finalize(() => this.modalBusy.set(false))
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: defRes => {
this.permissionDefinitions.set(defRes.permissions || []);
this.hasResourcePermission.set((defRes.permissions?.length || 0) > 0);
this.state.setDefinitions(defRes.permissions || []);
},
error: () => {
this.toasterService.error('AbpPermissionManagement::ErrorLoadingPermissions');
@ -173,190 +117,29 @@ export class ResourcePermissionManagementComponent implements OnInit {
});
}
resetState() {
this.viewMode.set('list');
this.allResourcePermissions.set([]);
this.resourcePermissions.set([]);
this.totalCount.set(0);
this.selectedProviderName.set('');
this.selectedProviderKey.set('');
this.searchFilter.set('');
this.selectedPermissions.set([]);
this.searchResults.set([]);
onAddClicked() {
this.state.goToAddMode();
}
goToAddMode() {
this.viewMode.set('add');
this.selectedPermissions.set([]);
this.selectedProviderKey.set('');
this.searchResults.set([]);
}
goToEditMode(grant: ResourcePermissionGrantInfoDto) {
this.editProviderName.set(grant.providerName || '');
this.editProviderKey.set(grant.providerKey || '');
this.modalBusy.set(true);
onEditClicked(grant: ResourcePermissionGrantInfoDto) {
this.state.prepareEditMode(grant);
this.state.modalBusy.set(true);
this.service.getResourceByProvider(
this.resourceName,
this.resourceKey,
this.resourceName(),
this.resourceKey(),
grant.providerName || '',
grant.providerKey || ''
).pipe(
finalize(() => this.modalBusy.set(false))
).subscribe({
next: res => {
this.permissionsWithProvider.set(res.permissions || []);
this.selectedPermissions.set(
(res.permissions || []).filter(p => p.isGranted).map(p => p.name || '')
);
this.viewMode.set('edit');
}
});
}
goToListMode() {
this.viewMode.set('list');
this.selectedPermissions.set([]);
}
onProviderChange(providerName: string) {
this.selectedProviderName.set(providerName);
this.selectedProviderKey.set('');
this.searchResults.set([]);
this.searchFilter.set('');
}
onSearchInput(filter: string) {
this.searchFilter.set(filter);
this.showDropdown.set(true);
this.searchSubject.next(filter);
}
onSearchFocus() {
this.showDropdown.set(true);
this.loadProviderKeys(this.searchFilter() || '');
}
onSearchBlur() {
setTimeout(() => {
this.showDropdown.set(false);
}, 200);
}
private loadProviderKeys(filter: string) {
if (!this.selectedProviderName()) return;
this.service.searchResourceProviderKey(
this.resourceName,
this.selectedProviderName(),
filter,
1
).subscribe(res => {
this.searchResults.set(res.keys || []);
});
}
private performSearch(filter: string) {
this.loadProviderKeys(filter);
}
selectProviderKey(key: SearchProviderKeyInfo) {
this.selectedProviderKey.set(key.providerKey || '');
this.searchFilter.set(key.providerDisplayName || key.providerKey || '');
this.searchResults.set([]);
this.showDropdown.set(false);
}
togglePermission(permissionName: string) {
const current = this.selectedPermissions();
if (current.includes(permissionName)) {
this.selectedPermissions.set(current.filter(p => p !== permissionName));
} else {
this.selectedPermissions.set([...current, permissionName]);
}
}
toggleAllPermissions(selectAll: boolean) {
if (this.viewMode() === 'add') {
this.selectedPermissions.set(
selectAll
? this.permissionDefinitions().map(p => p.name || '')
: []
);
} else {
this.selectedPermissions.set(
selectAll
? this.permissionsWithProvider().map(p => p.name || '')
: []
);
}
}
isPermissionSelected(permissionName: string): boolean {
return this.selectedPermissions().includes(permissionName);
}
allPermissionsSelected = computed(() => {
const definitions = this.viewMode() === 'add'
? this.permissionDefinitions()
: this.permissionsWithProvider();
return definitions.length > 0 &&
definitions.every(p => this.selectedPermissions().includes(p.name || ''));
});
saveAddPermission() {
if (!this.selectedProviderKey() || this.selectedPermissions().length === 0) {
this.toasterService.warn('AbpPermissionManagement::PleaseSelectProviderAndPermissions');
return;
}
this.modalBusy.set(true);
this.service.updateResource(
this.resourceName,
this.resourceKey,
{
providerName: this.selectedProviderName(),
providerKey: this.selectedProviderKey(),
permissions: this.selectedPermissions()
}
).pipe(
switchMap(() => this.service.getResource(this.resourceName, this.resourceKey)),
finalize(() => this.modalBusy.set(false))
).subscribe({
next: res => {
this.allResourcePermissions.set(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::SavedSuccessfully');
this.goToListMode();
}
});
}
saveEditPermission() {
this.modalBusy.set(true);
this.service.updateResource(
this.resourceName,
this.resourceKey,
{
providerName: this.editProviderName(),
providerKey: this.editProviderKey(),
permissions: this.selectedPermissions()
}
).pipe(
switchMap(() => this.service.getResource(this.resourceName, this.resourceKey)),
finalize(() => this.modalBusy.set(false))
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: res => {
this.allResourcePermissions.set(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::SavedSuccessfully');
this.goToListMode();
this.state.setEditModePermissions(res.permissions || []);
}
});
}
deletePermission(grant: ResourcePermissionGrantInfoDto) {
onDeleteClicked(grant: ResourcePermissionGrantInfoDto) {
this.confirmationService
.warn(
'AbpPermissionManagement::PermissionDeletionConfirmationMessage',
@ -367,18 +150,18 @@ export class ResourcePermissionManagementComponent implements OnInit {
)
.subscribe((status: Confirmation.Status) => {
if (status === Confirmation.Status.confirm) {
this.modalBusy.set(true);
this.state.modalBusy.set(true);
this.service.deleteResource(
this.resourceName,
this.resourceKey,
this.resourceName(),
this.resourceKey(),
grant.providerName || '',
grant.providerKey || ''
).pipe(
switchMap(() => this.service.getResource(this.resourceName, this.resourceKey)),
finalize(() => this.modalBusy.set(false))
switchMap(() => this.service.getResource(this.resourceName(), this.resourceKey())),
finalize(() => this.state.modalBusy.set(false))
).subscribe({
next: res => {
this.allResourcePermissions.set(res.permissions || []);
this.state.setResourceData(res.permissions || []);
this.list.get();
this.toasterService.success('AbpUi::SuccessfullyDeleted');
}
@ -386,4 +169,36 @@ export class ResourcePermissionManagementComponent implements OnInit {
}
});
}
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();
}
});
}
}

1
npm/ng-packs/packages/permission-management/src/lib/services/index.ts

@ -1 +1,2 @@
export * from './extensions.service';
export * from './resource-permission-state.service';

165
npm/ng-packs/packages/permission-management/src/lib/services/resource-permission-state.service.ts

@ -0,0 +1,165 @@
import { Injectable, signal, computed } from '@angular/core';
import {
ResourcePermissionGrantInfoDto,
ResourceProviderDto,
ResourcePermissionDefinitionDto,
SearchProviderKeyInfo,
ResourcePermissionWithProdiverGrantInfoDto,
} from '@abp/ng.permission-management/proxy';
export type ViewMode = 'list' | 'add' | 'edit';
@Injectable()
export class ResourcePermissionStateService {
// View state
readonly viewMode = signal<ViewMode>('list');
readonly modalBusy = signal(false);
readonly hasResourcePermission = signal(false);
readonly hasProviderKeyLookupService = signal(false);
// Resource data
readonly resourceName = signal('');
readonly resourceKey = signal('');
readonly resourceDisplayName = signal<string | undefined>(undefined);
// Permissions data
readonly allResourcePermissions = signal<ResourcePermissionGrantInfoDto[]>([]);
readonly resourcePermissions = signal<ResourcePermissionGrantInfoDto[]>([]);
readonly totalCount = signal(0);
readonly permissionDefinitions = signal<ResourcePermissionDefinitionDto[]>([]);
readonly permissionsWithProvider = signal<ResourcePermissionWithProdiverGrantInfoDto[]>([]);
readonly selectedPermissions = signal<string[]>([]);
// Provider data
readonly providers = signal<ResourceProviderDto[]>([]);
readonly selectedProviderName = signal('');
readonly selectedProviderKey = signal('');
// Edit mode specific
readonly editProviderName = signal('');
readonly editProviderKey = signal('');
// Search state
readonly searchFilter = signal('');
readonly searchResults = signal<SearchProviderKeyInfo[]>([]);
readonly showDropdown = signal(false);
// Computed properties
readonly isAddMode = computed(() => this.viewMode() === 'add');
readonly isEditMode = computed(() => this.viewMode() === 'edit');
readonly isListMode = computed(() => this.viewMode() === 'list');
readonly currentPermissionsList = computed(() =>
this.isAddMode() ? this.permissionDefinitions() : this.permissionsWithProvider()
);
readonly allPermissionsSelected = computed(() => {
const definitions = this.currentPermissionsList();
return definitions.length > 0 &&
definitions.every(p => this.selectedPermissions().includes(p.name || ''));
});
readonly canSave = computed(() => {
if (this.isAddMode()) {
return !!this.selectedProviderKey() && this.selectedPermissions().length > 0;
}
return this.selectedPermissions().length >= 0;
});
// State transition methods
goToListMode() {
this.viewMode.set('list');
this.selectedPermissions.set([]);
}
goToAddMode() {
this.viewMode.set('add');
this.selectedPermissions.set([]);
this.selectedProviderKey.set('');
this.searchResults.set([]);
this.searchFilter.set('');
}
prepareEditMode(grant: ResourcePermissionGrantInfoDto) {
this.editProviderName.set(grant.providerName || '');
this.editProviderKey.set(grant.providerKey || '');
}
setEditModePermissions(permissions: ResourcePermissionWithProdiverGrantInfoDto[]) {
this.permissionsWithProvider.set(permissions);
this.selectedPermissions.set(
permissions.filter(p => p.isGranted).map(p => p.name || '')
);
this.viewMode.set('edit');
}
// Permission selection methods
togglePermission(permissionName: string) {
const current = this.selectedPermissions();
if (current.includes(permissionName)) {
this.selectedPermissions.set(current.filter(p => p !== permissionName));
} else {
this.selectedPermissions.set([...current, permissionName]);
}
}
toggleAllPermissions(selectAll: boolean) {
const permissions = this.currentPermissionsList();
this.selectedPermissions.set(
selectAll ? permissions.map(p => p.name || '') : []
);
}
isPermissionSelected(permissionName: string): boolean {
return this.selectedPermissions().includes(permissionName);
}
// Provider search methods
onProviderChange(providerName: string) {
this.selectedProviderName.set(providerName);
this.selectedProviderKey.set('');
this.searchResults.set([]);
this.searchFilter.set('');
}
selectProviderKey(key: SearchProviderKeyInfo) {
this.selectedProviderKey.set(key.providerKey || '');
this.searchFilter.set(key.providerDisplayName || key.providerKey || '');
this.searchResults.set([]);
this.showDropdown.set(false);
}
// Reset all state
reset() {
this.viewMode.set('list');
this.allResourcePermissions.set([]);
this.resourcePermissions.set([]);
this.totalCount.set(0);
this.selectedProviderName.set('');
this.selectedProviderKey.set('');
this.searchFilter.set('');
this.selectedPermissions.set([]);
this.searchResults.set([]);
this.editProviderName.set('');
this.editProviderKey.set('');
}
// Data loading helpers
setResourceData(permissions: ResourcePermissionGrantInfoDto[]) {
this.allResourcePermissions.set(permissions);
this.totalCount.set(permissions.length);
}
setProviders(providers: ResourceProviderDto[]) {
this.providers.set(providers);
this.hasProviderKeyLookupService.set(providers.length > 0);
if (providers.length) {
this.selectedProviderName.set(providers[0].name || '');
}
}
setDefinitions(permissions: ResourcePermissionDefinitionDto[]) {
this.permissionDefinitions.set(permissions);
this.hasResourcePermission.set(permissions.length > 0);
}
}
Loading…
Cancel
Save