mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-26 12:43:19 +00:00
315 lines
8.4 KiB
TypeScript
315 lines
8.4 KiB
TypeScript
import { KeyValue, KeyValuePipe } from '@angular/common'
|
|
import { Component, forwardRef, inject, Input, OnInit } from '@angular/core'
|
|
import {
|
|
AbstractControl,
|
|
ControlValueAccessor,
|
|
FormControl,
|
|
FormGroup,
|
|
FormsModule,
|
|
NG_VALUE_ACCESSOR,
|
|
ReactiveFormsModule,
|
|
} from '@angular/forms'
|
|
import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'
|
|
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
|
|
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
|
|
import {
|
|
PermissionAction,
|
|
PermissionsService,
|
|
PermissionType,
|
|
} from 'src/app/services/permissions.service'
|
|
import { SettingsService } from 'src/app/services/settings.service'
|
|
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
|
|
|
|
@Component({
|
|
providers: [
|
|
{
|
|
provide: NG_VALUE_ACCESSOR,
|
|
useExisting: forwardRef(() => PermissionsSelectComponent),
|
|
multi: true,
|
|
},
|
|
],
|
|
selector: 'pngx-permissions-select',
|
|
templateUrl: './permissions-select.component.html',
|
|
styleUrls: ['./permissions-select.component.scss'],
|
|
imports: [
|
|
KeyValuePipe,
|
|
NgxBootstrapIconsModule,
|
|
NgbPopoverModule,
|
|
FormsModule,
|
|
ReactiveFormsModule,
|
|
],
|
|
})
|
|
export class PermissionsSelectComponent
|
|
extends ComponentWithPermissions
|
|
implements OnInit, ControlValueAccessor
|
|
{
|
|
private readonly permissionsService = inject(PermissionsService)
|
|
private readonly settingsService = inject(SettingsService)
|
|
|
|
@Input()
|
|
title: string = 'Permissions'
|
|
|
|
@Input()
|
|
error: string
|
|
|
|
permissions: string[]
|
|
|
|
form = new FormGroup({})
|
|
|
|
typesWithAllActions: Set<string> = new Set()
|
|
|
|
private readonly actionOrder = [
|
|
PermissionAction.Add,
|
|
PermissionAction.Change,
|
|
PermissionAction.Delete,
|
|
PermissionAction.View,
|
|
]
|
|
|
|
_inheritedPermissions: string[] = []
|
|
|
|
@Input()
|
|
set inheritedPermissions(inherited: string[]) {
|
|
// remove <app_label>. from permission strings
|
|
const newInheritedPermissions = inherited?.length
|
|
? inherited.map((p) => p.replace(/^\w+\./g, ''))
|
|
: []
|
|
|
|
const changed =
|
|
newInheritedPermissions.length !== this._inheritedPermissions.length ||
|
|
newInheritedPermissions.some(
|
|
(p) => !this._inheritedPermissions.includes(p)
|
|
)
|
|
|
|
if (changed) {
|
|
// skip inherited permissions, these are the explicitly set ones
|
|
this.permissions = this.getSelectedPermissions(
|
|
this.form.getRawValue()
|
|
).filter((p) => !this._inheritedPermissions.includes(p))
|
|
this._inheritedPermissions = newInheritedPermissions
|
|
this.applyCheckedState()
|
|
} else {
|
|
this.updateDisabledStates()
|
|
}
|
|
}
|
|
|
|
inheritedWarning: string = $localize`Inherited from group`
|
|
|
|
public allowedTypes = Object.keys(PermissionType)
|
|
|
|
constructor() {
|
|
super()
|
|
if (!this.settingsService.get(SETTINGS_KEYS.AUDITLOG_ENABLED)) {
|
|
this.allowedTypes.splice(this.allowedTypes.indexOf('History'), 1)
|
|
}
|
|
this.allowedTypes.forEach((type) => {
|
|
const control = new FormGroup({})
|
|
for (const action of Object.keys(PermissionAction)) {
|
|
control.addControl(action, new FormControl(null))
|
|
}
|
|
this.form.addControl(type, control)
|
|
})
|
|
}
|
|
|
|
writeValue(permissions: string[]): void {
|
|
if (this.permissions === permissions) {
|
|
return
|
|
}
|
|
|
|
this.permissions = permissions ?? []
|
|
this.applyCheckedState()
|
|
}
|
|
|
|
// sets every checkbox from inherited + own perms
|
|
private applyCheckedState(): void {
|
|
const allPerms = new Set(
|
|
this._inheritedPermissions.concat(this.permissions)
|
|
)
|
|
|
|
this.allowedTypes.forEach((type) => {
|
|
const typeGroup = this.form.get(type)
|
|
for (const action of Object.keys(PermissionAction)) {
|
|
typeGroup.get(action)?.patchValue(
|
|
allPerms.has(
|
|
this.permissionsService.getPermissionCode(
|
|
PermissionAction[action],
|
|
PermissionType[type]
|
|
)
|
|
),
|
|
{ emitEvent: false } // don't trigger valueChanges now
|
|
)
|
|
}
|
|
|
|
if (this.typeHasAllActionsSelected(type)) {
|
|
this.typesWithAllActions.add(type)
|
|
} else {
|
|
this.typesWithAllActions.delete(type)
|
|
}
|
|
})
|
|
|
|
this.updateDisabledStates()
|
|
}
|
|
|
|
onChange = (newValue: string[]) => {}
|
|
|
|
onTouched = () => {}
|
|
|
|
disabled: boolean = false
|
|
|
|
registerOnChange(fn: any): void {
|
|
this.onChange = fn
|
|
}
|
|
|
|
registerOnTouched(fn: any): void {
|
|
this.onTouched = fn
|
|
}
|
|
|
|
setDisabledState?(isDisabled: boolean): void {
|
|
this.disabled = isDisabled
|
|
}
|
|
|
|
ngOnInit(): void {
|
|
this.form.valueChanges.subscribe((newValue) => {
|
|
const permissions = this.getSelectedPermissions(newValue)
|
|
|
|
Object.keys(newValue).forEach((typeKey) => {
|
|
if (this.typeHasAllActionsSelected(typeKey)) {
|
|
this.typesWithAllActions.add(typeKey)
|
|
} else {
|
|
this.typesWithAllActions.delete(typeKey)
|
|
}
|
|
})
|
|
|
|
this.onChange(
|
|
permissions.filter((p) => !this._inheritedPermissions.includes(p))
|
|
)
|
|
})
|
|
|
|
this.updateDisabledStates()
|
|
}
|
|
|
|
toggleAll(event, type) {
|
|
const typeGroup = this.form.get(type)
|
|
Object.keys(PermissionAction)
|
|
.filter((action) =>
|
|
this.isActionSupported(PermissionType[type], PermissionAction[action])
|
|
)
|
|
.forEach((action) => {
|
|
typeGroup.get(action).patchValue(event.target.checked)
|
|
})
|
|
|
|
if (this.typeHasAllActionsSelected(type)) {
|
|
this.typesWithAllActions.add(type)
|
|
} else {
|
|
this.typesWithAllActions.delete(type)
|
|
}
|
|
}
|
|
|
|
isInherited(typeKey: string, actionKey: string = null) {
|
|
if (this._inheritedPermissions.length == 0) return false
|
|
else if (actionKey) {
|
|
return this._inheritedPermissions.includes(
|
|
this.permissionsService.getPermissionCode(
|
|
PermissionAction[actionKey],
|
|
PermissionType[typeKey]
|
|
)
|
|
)
|
|
} else {
|
|
return Object.keys(PermissionAction)
|
|
.filter((action) =>
|
|
this.isActionSupported(
|
|
PermissionType[typeKey],
|
|
PermissionAction[action]
|
|
)
|
|
)
|
|
.every((action) => {
|
|
return this._inheritedPermissions.includes(
|
|
this.permissionsService.getPermissionCode(
|
|
PermissionAction[action],
|
|
PermissionType[typeKey]
|
|
)
|
|
)
|
|
})
|
|
}
|
|
}
|
|
|
|
updateDisabledStates() {
|
|
this.allowedTypes.forEach((type) => {
|
|
const control = this.form.get(type)
|
|
let actionControl: AbstractControl
|
|
for (const action of Object.keys(PermissionAction)) {
|
|
actionControl = control.get(action)
|
|
if (
|
|
!this.isActionSupported(
|
|
PermissionType[type],
|
|
PermissionAction[action]
|
|
)
|
|
) {
|
|
actionControl.patchValue(false, { emitEvent: false })
|
|
actionControl.disable({ emitEvent: false })
|
|
continue
|
|
}
|
|
|
|
this.isInherited(type, action) || this.disabled
|
|
? actionControl.disable({ emitEvent: false })
|
|
: actionControl.enable({ emitEvent: false })
|
|
}
|
|
})
|
|
}
|
|
|
|
public isActionSupported(
|
|
type: PermissionType,
|
|
action: PermissionAction
|
|
): boolean {
|
|
// Global statistics and system status only support view
|
|
if (
|
|
type === PermissionType.GlobalStatistics ||
|
|
type === PermissionType.SystemMonitoring
|
|
) {
|
|
return action === PermissionAction.View
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
private getSelectedPermissions(formValue: object): string[] {
|
|
const permissions = []
|
|
Object.entries(formValue).forEach(([typeKey, typeValue]) => {
|
|
Object.entries(typeValue)
|
|
.filter(
|
|
([actionKey, actionValue]) =>
|
|
actionValue &&
|
|
this.isActionSupported(
|
|
PermissionType[typeKey],
|
|
PermissionAction[actionKey]
|
|
)
|
|
)
|
|
.forEach(([actionKey]) => {
|
|
permissions.push(
|
|
this.permissionsService.getPermissionCode(
|
|
PermissionAction[actionKey],
|
|
PermissionType[typeKey]
|
|
)
|
|
)
|
|
})
|
|
})
|
|
return permissions
|
|
}
|
|
|
|
private typeHasAllActionsSelected(typeKey: string): boolean {
|
|
return Object.keys(PermissionAction)
|
|
.filter((action) =>
|
|
this.isActionSupported(
|
|
PermissionType[typeKey],
|
|
PermissionAction[action]
|
|
)
|
|
)
|
|
.every((action) => !!this.form.get(typeKey)?.get(action)?.value)
|
|
}
|
|
|
|
public sortActions = (
|
|
a: KeyValue<string, PermissionAction>,
|
|
b: KeyValue<string, PermissionAction>
|
|
): number =>
|
|
this.actionOrder.indexOf(a.value) - this.actionOrder.indexOf(b.value)
|
|
}
|