Refactor permissions to use enums, permissions service

This commit is contained in:
Michael Shamoon 2022-11-12 04:03:35 -08:00
parent 59e359ff98
commit 96a29883cd
39 changed files with 335 additions and 134 deletions

View File

@ -14,9 +14,13 @@ import { DocumentAsnComponent } from './components/document-asn/document-asn.com
import { DirtyFormGuard } from './guards/dirty-form.guard'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
import { TasksComponent } from './components/manage/tasks/tasks.component'
import { AuthGard } from './guards/auth.gard'
import { PermissionsGuard } from './guards/permissions.guard'
import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import {
PermissionAction,
PermissionType,
} from './services/permissions.service'
const routes: Routes = [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
@ -30,70 +34,125 @@ const routes: Routes = [
path: 'documents',
component: DocumentListComponent,
canDeactivate: [DirtySavedViewGuard],
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_document' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Document,
},
},
},
{
path: 'view/:id',
component: DocumentListComponent,
canDeactivate: [DirtySavedViewGuard],
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_savedview' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.SavedView,
},
},
},
{
path: 'documents/:id',
component: DocumentDetailComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_document' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Document,
},
},
},
{
path: 'asn/:id',
component: DocumentAsnComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_document' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Document,
},
},
},
{
path: 'tags',
component: TagListComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_tag' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Tag,
},
},
},
{
path: 'documenttypes',
component: DocumentTypeListComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_documenttype' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.DocumentType,
},
},
},
{
path: 'correspondents',
component: CorrespondentListComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_correspondent' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Correspondent,
},
},
},
{
path: 'storagepaths',
component: StoragePathListComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_storagepath' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.StoragePath,
},
},
},
{
path: 'logs',
component: LogsComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_log' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.Log,
},
},
},
{
path: 'settings',
component: SettingsComponent,
canDeactivate: [DirtyFormGuard],
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_uisettings' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.UISettings,
},
},
},
{
path: 'tasks',
component: TasksComponent,
canActivate: [AuthGard],
data: { requiredPermission: 'documents.view_paperlesstask' },
canActivate: [PermissionsGuard],
data: {
requiredPermission: {
action: PermissionAction.View,
type: PermissionType.PaperlessTask,
},
},
},
],
},

View File

@ -9,6 +9,11 @@ import { NgxFileDropEntry } from 'ngx-file-drop'
import { UploadDocumentsService } from './services/upload-documents.service'
import { TasksService } from './services/tasks.service'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from './services/permissions.service'
@Component({
selector: 'app-root',
@ -32,7 +37,8 @@ export class AppComponent implements OnInit, OnDestroy {
private uploadDocumentsService: UploadDocumentsService,
private tasksService: TasksService,
public tourService: TourService,
private renderer: Renderer2
private renderer: Renderer2,
private permissionsService: PermissionsService
) {
let anyWindow = window as any
anyWindow.pdfWorkerSrc = 'assets/js/pdf.worker.min.js'
@ -74,7 +80,12 @@ export class AppComponent implements OnInit, OnDestroy {
if (
this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS)
) {
if (this.settings.currentUserCan('documents.view_document')) {
if (
this.permissionsService.currentUserCan({
action: PermissionAction.View,
type: PermissionType.Document,
})
) {
this.toastService.show({
title: $localize`Document added`,
delay: 10000,
@ -209,7 +220,10 @@ export class AppComponent implements OnInit, OnDestroy {
public get dragDropEnabled(): boolean {
return (
!this.router.url.includes('dashboard') &&
this.settings.currentUserCan('documents.add_document')
this.permissionsService.currentUserCan({
action: PermissionAction.Add,
type: PermissionType.Document,
})
)
}

View File

@ -70,7 +70,7 @@ import { ColorSliderModule } from 'ngx-color/slider'
import { ColorComponent } from './components/common/input/color/color.component'
import { DocumentAsnComponent } from './components/document-asn/document-asn.component'
import { DocumentCommentsComponent } from './components/document-comments/document-comments.component'
import { AuthGard } from './guards/auth.gard'
import { PermissionsGuard } from './guards/permissions.guard'
import { DirtyDocGuard } from './guards/dirty-doc.guard'
import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard'
import { StoragePathListComponent } from './components/manage/storage-path-list/storage-path-list.component'
@ -220,7 +220,7 @@ function initializeApp(settings: SettingsService) {
DocumentTitlePipe,
{ provide: NgbDateAdapter, useClass: ISODateAdapter },
{ provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter },
AuthGard,
PermissionsGuard,
DirtyDocGuard,
DirtySavedViewGuard,
],

View File

@ -10,7 +10,7 @@
</svg>
<span class="ms-2" [class.visually-hidden]="slimSidebarEnabled" i18n="app title">Paperless-ngx</span>
</a>
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1" *ifPermissions="'documents.view_document'">
<div class="search-form-container flex-grow-1 py-2 pb-3 pb-sm-2 px-3 ps-md-4 me-sm-auto order-3 order-sm-1" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<form (ngSubmit)="search()" class="form-inline flex-grow-1">
<svg width="1em" height="1em" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#search"/>
@ -39,7 +39,7 @@
<p class="small mb-0 px-3 text-muted" i18n>Logged in as {{this.settingsService.displayName}}</p>
<div class="dropdown-divider"></div>
</div>
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *ifPermissions="'documents.view_uisettings'">
<a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }">
<svg class="sidebaricon me-2" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
</svg><ng-container i18n>Settings</ng-container>
@ -72,7 +72,7 @@
</svg><span>&nbsp;<ng-container i18n>Dashboard</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_document'">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<a class="nav-link" routerLink="documents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Documents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#files"/>
@ -80,7 +80,7 @@
</a>
</li>
</ul>
<div *ifPermissions="'documents.view_savedview'">
<div *ifPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='savedViewService.loading || savedViewService.sidebarViews.length > 0'>
<span i18n>Saved views</span>
<div *ngIf="savedViewService.loading" class="spinner-border spinner-border-sm fw-normal ms-2" role="status"></div>
@ -96,7 +96,7 @@
</ul>
</div>
<div *ifPermissions="'documents.view_document'">
<div *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'>
<span i18n>Open documents</span>
</h6>
@ -127,35 +127,35 @@
<span i18n>Manage</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item" *ifPermissions="'documents.view_correspondent'">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
<a class="nav-link" routerLink="correspondents" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Correspondents" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#person"/>
</svg><span>&nbsp;<ng-container i18n>Correspondents</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_tag'" tourAnchor="tour.tags">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }" tourAnchor="tour.tags">
<a class="nav-link" routerLink="tags" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Tags" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#tags"/>
</svg><span>&nbsp;<ng-container i18n>Tags</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_documenttype'">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
<a class="nav-link" routerLink="documenttypes" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Document types" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#hash"/>
</svg><span>&nbsp;<ng-container i18n>Document types</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_storagepath'">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
<a class="nav-link" routerLink="storagepaths" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Storage paths" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#folder"/>
</svg><span>&nbsp;<ng-container i18n>Storage paths</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_paperlesstask'" tourAnchor="tour.file-tasks">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.PaperlessTask }" tourAnchor="tour.file-tasks">
<a class="nav-link" routerLink="tasks" routerLinkActive="active" (click)="closeMenu()" ngbPopover="File Tasks" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<span *ngIf="tasksService.failedFileTasks.length > 0 && slimSidebarEnabled" class="badge bg-danger position-absolute top-0 end-0">{{tasksService.failedFileTasks.length}}</span>
<svg class="sidebaricon" fill="currentColor">
@ -163,21 +163,21 @@
</svg><span>&nbsp;<ng-container i18n>File Tasks<span *ngIf="tasksService.failedFileTasks.length > 0"><span class="badge bg-danger ms-2">{{tasksService.failedFileTasks.length}}</span></span></ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_log'">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Log }">
<a class="nav-link" routerLink="logs" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Logs" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#text-left"/>
</svg><span>&nbsp;<ng-container i18n>Logs</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'documents.view_uisettings'" tourAnchor="tour.settings">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.UISettings }" tourAnchor="tour.settings">
<a class="nav-link" routerLink="settings" routerLinkActive="active" (click)="closeMenu()" ngbPopover="Settings" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#gear"/>
</svg><span>&nbsp;<ng-container i18n>Settings</ng-container></span>
</a>
</li>
<li class="nav-item" *ifPermissions="'admin.view_logentry'" tourAnchor="tour.admin">
<li class="nav-item" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Admin }" tourAnchor="tour.admin">
<a class="nav-link" href="admin/" ngbPopover="Admin" i18n-ngbPopover [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#toggles"/>

View File

@ -26,13 +26,17 @@ import { TasksService } from 'src/app/services/tasks.service'
import { ComponentCanDeactivate } from 'src/app/guards/dirty-doc.guard'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
@Component({
selector: 'app-app-frame',
templateUrl: './app-frame.component.html',
styleUrls: ['./app-frame.component.scss'],
})
export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
export class AppFrameComponent
extends ComponentWithPermissions
implements OnInit, ComponentCanDeactivate
{
constructor(
public router: Router,
private activatedRoute: ActivatedRoute,
@ -44,7 +48,9 @@ export class AppFrameComponent implements OnInit, ComponentCanDeactivate {
public settingsService: SettingsService,
public tasksService: TasksService,
private readonly toastService: ToastService
) {}
) {
super()
}
ngOnInit(): void {
if (this.settingsService.get(SETTINGS_KEYS.UPDATE_CHECKING_ENABLED)) {

View File

@ -28,7 +28,7 @@
<app-welcome-widget *ngIf="settingsService.offerTour()" tourAnchor="tour.dashboard"></app-welcome-widget>
<div *ifPermissions="'documents.view_savedview'">
<div *ifPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
<ng-container *ngFor="let v of savedViewService.dashboardViews; first as isFirst">
<app-saved-view-widget *ngIf="isFirst; else noTour" [savedView]="v" tourAnchor="tour.dashboard"></app-saved-view-widget>
<ng-template #noTour>

View File

@ -1,17 +1,20 @@
import { Component } from '@angular/core'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.scss'],
})
export class DashboardComponent {
export class DashboardComponent extends ComponentWithPermissions {
constructor(
public savedViewService: SavedViewService,
public settingsService: SettingsService
) {}
) {
super()
}
get subtitle() {
if (this.settingsService.displayName) {

View File

@ -1,6 +1,6 @@
<app-widget-frame [title]="savedView.name" [loading]="loading">
<a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *ifPermissions="'documents.view_document'" i18n>Show all</a>
<a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" i18n>Show all</a>
<table content class="table table-sm table-hover table-borderless mb-0">
@ -10,7 +10,7 @@
<th scope="col" i18n>Title</th>
</tr>
</thead>
<tbody *ifPermissions="'documents.view_document'">
<tbody *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<tr *ngFor="let doc of documents" (click)="openDocumentsService.openDocument(doc)">
<td>{{doc.created_date | customDate}}</td>
<td>{{doc.title | documentTitle}}<app-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t); $event.stopPropagation();"></app-tag></td>

View File

@ -9,13 +9,17 @@ import { PaperlessTag } from 'src/app/data/paperless-tag'
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
@Component({
selector: 'app-saved-view-widget',
templateUrl: './saved-view-widget.component.html',
styleUrls: ['./saved-view-widget.component.scss'],
})
export class SavedViewWidgetComponent implements OnInit, OnDestroy {
export class SavedViewWidgetComponent
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
loading: boolean = true
constructor(
@ -24,7 +28,9 @@ export class SavedViewWidgetComponent implements OnInit, OnDestroy {
private list: DocumentListViewService,
private consumerStatusService: ConsumerStatusService,
public openDocumentsService: OpenDocumentsService
) {}
) {
super()
}
@Input()
savedView: PaperlessSavedView

View File

@ -9,7 +9,7 @@
</a>
</div>
<div content tourAnchor="tour.upload-widget">
<form *ifPermissions="'documents.add_document'">
<form *ifPermissions="{ action: PermissionAction.Add, type: PermissionType.Document }">
<ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
(onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card"
multiple="true" contentClassName="justify-content-center d-flex align-items-center py-5 px-2" [showBrowseBtn]=true
@ -40,7 +40,7 @@
<h6 class="alert-heading">{{status.filename}}</h6>
<p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
<ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
<div *ifPermissions="'documents.view_document'">
<div *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<div *ngIf="isFinished(status)">
<button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
<small i18n>Open document</small>

View File

@ -1,6 +1,7 @@
import { HttpEventType } from '@angular/common/http'
import { Component, OnInit } from '@angular/core'
import { FileSystemFileEntry, NgxFileDropEntry } from 'ngx-file-drop'
import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
import {
ConsumerStatusService,
FileStatus,
@ -15,13 +16,18 @@ const MAX_ALERTS = 5
templateUrl: './upload-file-widget.component.html',
styleUrls: ['./upload-file-widget.component.scss'],
})
export class UploadFileWidgetComponent implements OnInit {
export class UploadFileWidgetComponent
extends ComponentWithPermissions
implements OnInit
{
alertsExpanded = false
constructor(
private consumerStatusService: ConsumerStatusService,
private uploadDocumentsService: UploadDocumentsService
) {}
) {
super()
}
getStatus() {
return this.consumerStatusService.getConsumerStatus().slice(0, MAX_ALERTS)

View File

@ -1,5 +1,5 @@
<div *ngIf="comments">
<form [formGroup]="commentForm" class="needs-validation mt-3" *ifPermissions="'documents.add_comment'" novalidate>
<form [formGroup]="commentForm" class="needs-validation mt-3" *ifPermissions="{ action: PermissionAction.Add, type: PermissionType.Comment }" novalidate>
<div class="form-group">
<textarea class="form-control form-control-sm" [class.is-invalid]="newCommentError" rows="3" formControlName="newComment" placeholder="Enter comment" i18n-placeholder required></textarea>
<div class="invalid-feedback" i18n>
@ -18,7 +18,7 @@
</div>
<div class="d-flex card-footer small bg-light text-primary justify-content-between align-items-center">
<span>{{displayName(comment)}} - {{ comment.created | customDate}}</span>
<button type="button" class="btn btn-link btn-sm p-0 fade" (click)="deleteComment(comment.id)" *ifPermissions="'documents.delete_comment'">
<button type="button" class="btn btn-link btn-sm p-0 fade" (click)="deleteComment(comment.id)" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Comment }">
<svg width="13" height="13" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>

View File

@ -4,13 +4,14 @@ import { PaperlessDocumentComment } from 'src/app/data/paperless-document-commen
import { FormControl, FormGroup } from '@angular/forms'
import { first } from 'rxjs/operators'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
@Component({
selector: 'app-document-comments',
templateUrl: './document-comments.component.html',
styleUrls: ['./document-comments.component.scss'],
})
export class DocumentCommentsComponent {
export class DocumentCommentsComponent extends ComponentWithPermissions {
commentForm: FormGroup = new FormGroup({
newComment: new FormControl(''),
})
@ -32,7 +33,9 @@ export class DocumentCommentsComponent {
constructor(
private commentsService: DocumentCommentsService,
private toastService: ToastService
) {}
) {
super()
}
update(): void {
this.networkActive = true

View File

@ -5,7 +5,7 @@
<div class="input-group-text" i18n>of {{previewNumPages}}</div>
</div>
<button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" *ifPermissions="'documents.delete_document'">
<button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
<svg class="buttonicon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg><span class="d-none d-lg-inline ps-1" i18n>Delete</span>
@ -182,7 +182,7 @@
<button type="button" class="btn btn-outline-secondary" (click)="discard()" i18n [disabled]="networkActive || !(isDirty$ | async)">Discard</button>&nbsp;
<button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>&nbsp;
<button type="submit" class="btn btn-primary" *ifPermissions="'documents.change_document'" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>&nbsp;
<button type="submit" class="btn btn-primary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>&nbsp;
</form>
</div>

View File

@ -35,6 +35,11 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { StoragePathEditDialogComponent } from '../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import {
PermissionAction,
PermissionsService,
PermissionType,
} from 'src/app/services/permissions.service'
@Component({
selector: 'app-document-detail',
@ -106,6 +111,9 @@ export class DocumentDetailComponent
}
}
PermissionAction = PermissionAction
PermissionType = PermissionType
constructor(
private documentsService: DocumentService,
private route: ActivatedRoute,
@ -118,7 +126,8 @@ export class DocumentDetailComponent
private documentTitlePipe: DocumentTitlePipe,
private toastService: ToastService,
private settings: SettingsService,
private storagePathService: StoragePathService
private storagePathService: StoragePathService,
private permissionsService: PermissionsService
) {}
titleKeyUp(event) {
@ -555,7 +564,10 @@ export class DocumentDetailComponent
get commentsEnabled(): boolean {
return (
this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) &&
this.settings.currentUserCan('documents.view_comment')
this.permissionsService.currentUserCan({
action: PermissionAction.View,
type: PermissionType.Document,
})
)
}
}

View File

@ -23,7 +23,7 @@
</div>
<div class="w-100 d-xl-none"></div>
<div class="col-auto mb-2 mb-xl-0">
<div class="d-flex" *ifPermissions="'documents.change_document'">
<div class="d-flex" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
<label class="ms-auto mt-1 mb-0 me-2" i18n>Edit:</label>
<app-filterable-dropdown class="me-2 me-md-3" title="Tags" icon="tag-fill" i18n-title
filterPlaceholder="Filter tags" i18n-filterPlaceholder
@ -91,7 +91,7 @@
</div>
</div>
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions="'documents.delete_document'">
<button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.Document }">
<svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#trash" />
</svg>&nbsp;<ng-container i18n>Delete</ng-container>

View File

@ -25,13 +25,14 @@ import { saveAs } from 'file-saver'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
@Component({
selector: 'app-bulk-editor',
templateUrl: './bulk-editor.component.html',
styleUrls: ['./bulk-editor.component.scss'],
})
export class BulkEditorComponent {
export class BulkEditorComponent extends ComponentWithPermissions {
tags: PaperlessTag[]
correspondents: PaperlessCorrespondent[]
documentTypes: PaperlessDocumentType[]
@ -54,7 +55,9 @@ export class BulkEditorComponent {
private settings: SettingsService,
private toastService: ToastService,
private storagePathService: StoragePathService
) {}
) {
super()
}
applyOnClose: boolean = this.settings.get(
SETTINGS_KEYS.BULK_EDIT_APPLY_ON_CLOSE

View File

@ -37,7 +37,7 @@
<use xlink:href="assets/bootstrap-icons.svg#diagram-3"/>
</svg>&nbsp;<span class="d-none d-md-inline" i18n>More like this</span>
</a>
<a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" *ifPermissions="'documents.change_document'">
<a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }">
<svg class="sidebaricon" fill="currentColor" class="sidebaricon">
<use xlink:href="assets/bootstrap-icons.svg#pencil"/>
</svg>&nbsp;<span class="d-none d-md-inline" i18n>Edit</span>

View File

@ -11,9 +11,8 @@ import { DocumentService } from 'src/app/services/rest/document.service'
import { SettingsService } from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { FILTER_FULLTEXT_MORELIKE } from 'src/app/data/filter-rule-type'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
@Component({
selector: 'app-document-card-large',
@ -23,12 +22,17 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
'../popover-preview/popover-preview.scss',
],
})
export class DocumentCardLargeComponent implements OnInit {
export class DocumentCardLargeComponent
extends ComponentWithPermissions
implements OnInit
{
constructor(
private documentService: DocumentService,
private settingsService: SettingsService,
public openDocumentsService: OpenDocumentsService
) {}
) {
super()
}
@Input()
selected = false

View File

@ -67,7 +67,7 @@
</div>
<div class="d-flex justify-content-between align-items-center">
<div class="btn-group w-100">
<a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" title="Edit" *ifPermissions="'documents.change_document'" i18n-title>
<a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" title="Edit" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.Document }" i18n-title>
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>

View File

@ -2,7 +2,6 @@ import {
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewChild,
} from '@angular/core'
@ -13,6 +12,7 @@ import { SettingsService } from 'src/app/services/settings.service'
import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
import { OpenDocumentsService } from 'src/app/services/open-documents.service'
import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
@Component({
selector: 'app-document-card-small',
@ -22,12 +22,14 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
'../popover-preview/popover-preview.scss',
],
})
export class DocumentCardSmallComponent implements OnInit {
export class DocumentCardSmallComponent extends ComponentWithPermissions {
constructor(
private documentService: DocumentService,
private settingsService: SettingsService,
public openDocumentsService: OpenDocumentsService
) {}
) {
super()
}
@Input()
selected = false
@ -57,8 +59,6 @@ export class DocumentCardSmallComponent implements OnInit {
mouseOnPreview = false
popoverHidden = true
ngOnInit(): void {}
getIsThumbInverted() {
return this.settingsService.get(SETTINGS_KEYS.DARK_MODE_THUMB_INVERTED)
}

View File

@ -59,7 +59,7 @@
</div>
</div>
<div class="btn-group ms-2 flex-fill" *ifPermissions="'documents.view_savedview'" ngbDropdown role="group">
<div class="btn-group ms-2 flex-fill" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }" ngbDropdown role="group">
<button class="btn btn-sm btn-outline-primary dropdown-toggle flex-fill" tourAnchor="tour.documents-views" ngbDropdownToggle>
<ng-container i18n>Views</ng-container>
<div *ngIf="savedViewIsModified" class="position-absolute top-0 start-100 p-2 translate-middle badge bg-secondary border border-light rounded-circle">
@ -72,10 +72,10 @@
<div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div>
</ng-container>
<div *ifPermissions="'documents.change_savedviewfilterrule'">
<div *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.SavedView }">
<button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.activeSavedViewId" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button>
</div>
<button ngbDropdownItem (click)="saveViewConfigAs()" *ifPermissions="'documents.add_savedview'" i18n>Save as...</button>
<button ngbDropdownItem (click)="saveViewConfigAs()" *ifPermissions="{ action: PermissionAction.Add, type: PermissionType.SavedView }" i18n>Save as...</button>
</div>
</div>

View File

@ -30,6 +30,7 @@ import {
} from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { ToastService } from 'src/app/services/toast.service'
import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
import { FilterEditorComponent } from './filter-editor/filter-editor.component'
import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-view-config-dialog.component'
@ -38,7 +39,10 @@ import { SaveViewConfigDialogComponent } from './save-view-config-dialog/save-vi
templateUrl: './document-list.component.html',
styleUrls: ['./document-list.component.scss'],
})
export class DocumentListComponent implements OnInit, OnDestroy {
export class DocumentListComponent
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
constructor(
public list: DocumentListViewService,
public savedViewService: SavedViewService,
@ -48,7 +52,9 @@ export class DocumentListComponent implements OnInit, OnDestroy {
private modalService: NgbModal,
private consumerStatusService: ConsumerStatusService,
public openDocumentsService: OpenDocumentsService
) {}
) {
super()
}
@ViewChild('filterEditor')
private filterEditor: FilterEditorComponent

View File

@ -4,6 +4,7 @@ import { FILTER_CORRESPONDENT } from 'src/app/data/filter-rule-type'
import { PaperlessCorrespondent } from 'src/app/data/paperless-correspondent'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionType } from 'src/app/services/permissions.service'
import { CorrespondentService } from 'src/app/services/rest/correspondent.service'
import { ToastService } from 'src/app/services/toast.service'
import { CorrespondentEditDialogComponent } from '../../common/edit-dialog/correspondent-edit-dialog/correspondent-edit-dialog.component'
@ -32,7 +33,7 @@ export class CorrespondentListComponent extends ManagementListComponent<Paperles
FILTER_CORRESPONDENT,
$localize`correspondent`,
$localize`correspondents`,
'correspondent',
PermissionType.Correspondent,
[
{
key: 'last_correspondence',

View File

@ -3,6 +3,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_DOCUMENT_TYPE } from 'src/app/data/filter-rule-type'
import { PaperlessDocumentType } from 'src/app/data/paperless-document-type'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionType } from 'src/app/services/permissions.service'
import { DocumentTypeService } from 'src/app/services/rest/document-type.service'
import { ToastService } from 'src/app/services/toast.service'
import { DocumentTypeEditDialogComponent } from '../../common/edit-dialog/document-type-edit-dialog/document-type-edit-dialog.component'
@ -29,7 +30,7 @@ export class DocumentTypeListComponent extends ManagementListComponent<Paperless
FILTER_DOCUMENT_TYPE,
$localize`document type`,
$localize`document types`,
'documenttype',
PermissionType.DocumentType,
[]
)
}

View File

@ -1,5 +1,5 @@
<app-page-header title="{{ typeNamePlural | titlecase }}">
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *ifPermissions="'documents.add_' + permissionName" i18n>Create</button>
<button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *ifPermissions="{ action: PermissionAction.Add, type: permissionType }" i18n>Create</button>
</app-page-header>
<div class="row">
@ -41,24 +41,24 @@
</svg>
</button>
<div ngbDropdownMenu aria-labelledby="actionsMenuMobile">
<button (click)="filterDocuments(object)" *ifPermissions="'documents.view_document'" ngbDropdownItem i18n>Filter Documents</button>
<button (click)="openEditDialog(object)" *ifPermissions="'documents.change_' + permissionName" ngbDropdownItem i18n>Edit</button>
<button class="text-danger" (click)="openDeleteDialog(object)" *ifPermissions="'documents.delete_' + permissionName" ngbDropdownItem i18n>Delete</button>
<button (click)="filterDocuments(object)" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" ngbDropdownItem i18n>Filter Documents</button>
<button (click)="openEditDialog(object)" *ifPermissions="{ action: PermissionAction.Change, type: permissionType }" ngbDropdownItem i18n>Edit</button>
<button class="text-danger" (click)="openDeleteDialog(object)" *ifPermissions="{ action: PermissionAction.Delete, type: permissionType }" ngbDropdownItem i18n>Delete</button>
</div>
</div>
</div>
<div class="btn-group d-none d-sm-block">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)" *ifPermissions="'documents.view_document'">
<button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)" *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-funnel" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.5 1.5A.5.5 0 0 1 2 1h12a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-.128.334L10 8.692V13.5a.5.5 0 0 1-.342.474l-3 1A.5.5 0 0 1 6 14.5V8.692L1.628 3.834A.5.5 0 0 1 1.5 3.5v-2zm1 .5v1.308l4.372 4.858A.5.5 0 0 1 7 8.5v5.306l2-.666V8.5a.5.5 0 0 1 .128-.334L13.5 3.308V2h-11z"/>
</svg>&nbsp;<ng-container i18n>Documents</ng-container>
</button>
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)" *ifPermissions="'documents.change_' + permissionName">
<button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)" *ifPermissions="{ action: PermissionAction.Change, type: permissionType }">
<svg width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-pencil" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M12.146.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1 0 .708l-10 10a.5.5 0 0 1-.168.11l-5 2a.5.5 0 0 1-.65-.65l2-5a.5.5 0 0 1 .11-.168l10-10zM11.207 2.5L13.5 4.793 14.793 3.5 12.5 1.207 11.207 2.5zm1.586 3L10.5 3.207 4 9.707V10h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.293l6.5-6.5zm-9.761 5.175l-.106.106-1.528 3.821 3.821-1.528.106-.106A.5.5 0 0 1 5 12.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.468-.325z"/>
</svg>&nbsp;<ng-container i18n>Edit</ng-container>
</button>
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)" *ifPermissions="'documents.delete_' + permissionName">
<button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)" *ifPermissions="{ action: PermissionAction.Delete, type: permissionType }">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6z"/>
<path fill-rule="evenodd" d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1zM4.118 4L4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118zM2.5 3V2h11v1h-11z"/>

View File

@ -19,9 +19,11 @@ import {
SortEvent,
} from 'src/app/directives/sortable.directive'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionType } from 'src/app/services/permissions.service'
import { AbstractNameFilterService } from 'src/app/services/rest/abstract-name-filter-service'
import { ToastService } from 'src/app/services/toast.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
export interface ManagementListColumn {
key: string
@ -35,6 +37,7 @@ export interface ManagementListColumn {
@Directive()
export abstract class ManagementListComponent<T extends ObjectWithId>
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
constructor(
@ -46,9 +49,11 @@ export abstract class ManagementListComponent<T extends ObjectWithId>
protected filterRuleType: number,
public typeName: string,
public typeNamePlural: string,
public permissionName: string,
public permissionType: PermissionType,
public extraColumns: ManagementListColumn[]
) {}
) {
super()
}
@ViewChildren(SortableDirective) headers: QueryList<SortableDirective>

View File

@ -206,7 +206,7 @@
<div class="mb-2 col-auto">
<label class="form-label" for="name_{{view.id}}" i18n>Actions</label>
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *ifPermissions="'documents.delete_savedview'" i18n>Delete</button>
<button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *ifPermissions="{ action: PermissionAction.Delete, type: PermissionType.SavedView }" i18n>Delete</button>
</div>
</div>
@ -220,5 +220,5 @@
<div [ngbNavOutlet]="nav" class="border-start border-end border-bottom p-3 mb-3 shadow-sm"></div>
<button type="submit" class="btn btn-primary mb-2" *ifPermissions="'documents.change_uisettings'" [disabled]="!(isDirty$ | async)" i18n>Save</button>
<button type="submit" class="btn btn-primary mb-2" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.UISettings }" [disabled]="!(isDirty$ | async)" i18n>Save</button>
</form>

View File

@ -29,6 +29,7 @@ import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
import { ActivatedRoute } from '@angular/router'
import { ViewportScroller } from '@angular/common'
import { TourService } from 'ngx-ui-tour-ng-bootstrap'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
@Component({
selector: 'app-settings',
@ -36,6 +37,7 @@ import { TourService } from 'ngx-ui-tour-ng-bootstrap'
styleUrls: ['./settings.component.scss'],
})
export class SettingsComponent
extends ComponentWithPermissions
implements OnInit, AfterViewInit, OnDestroy, DirtyComponent
{
savedViewGroup = new FormGroup({})
@ -89,6 +91,7 @@ export class SettingsComponent
private activatedRoute: ActivatedRoute,
public readonly tourService: TourService
) {
super()
this.settings.settingsSaved.subscribe(() => {
if (!this.savePending) this.initialize()
})

View File

@ -3,6 +3,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_STORAGE_PATH } from 'src/app/data/filter-rule-type'
import { PaperlessStoragePath } from 'src/app/data/paperless-storage-path'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionType } from 'src/app/services/permissions.service'
import { StoragePathService } from 'src/app/services/rest/storage-path.service'
import { ToastService } from 'src/app/services/toast.service'
import { StoragePathEditDialogComponent } from '../../common/edit-dialog/storage-path-edit-dialog/storage-path-edit-dialog.component'
@ -29,7 +30,7 @@ export class StoragePathListComponent extends ManagementListComponent<PaperlessS
FILTER_STORAGE_PATH,
$localize`storage path`,
$localize`storage paths`,
'storagepath',
PermissionType.StoragePath,
[
{
key: 'path',

View File

@ -3,6 +3,7 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
import { PaperlessTag } from 'src/app/data/paperless-tag'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionType } from 'src/app/services/permissions.service'
import { TagService } from 'src/app/services/rest/tag.service'
import { ToastService } from 'src/app/services/toast.service'
import { TagEditDialogComponent } from '../../common/edit-dialog/tag-edit-dialog/tag-edit-dialog.component'
@ -29,7 +30,7 @@ export class TagListComponent extends ManagementListComponent<PaperlessTag> {
FILTER_HAS_TAGS_ALL,
$localize`tag`,
$localize`tags`,
'tag',
PermissionType.Tag,
[
{
key: 'color',

View File

@ -5,7 +5,7 @@
<use xlink:href="assets/bootstrap-icons.svg#x"/>
</svg>&nbsp;<ng-container i18n>Clear selection</ng-container>
</button>
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *ifPermissions="'django_q.delete_task'" [disabled]="tasksService.total == 0">
<button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }" [disabled]="tasksService.total == 0">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check2-all"/>
</svg>&nbsp;<ng-container i18n>{{dismissButtonText}}</ng-container>
@ -75,18 +75,18 @@
</td>
<td scope="row">
<div class="btn-group" role="group">
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *ifPermissions="'django_q.delete_task'">
<button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *ifPermissions="{ action: PermissionAction.Change, type: PermissionType.PaperlessTask }">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#check"/>
</svg>&nbsp;<ng-container i18n>Dismiss</ng-container>
</button>
<div *ifPermissions="'documents.view_document'"> <!-- TODO - This div breaks btn-group logic, may have to find a way to merge *ngIf and *ifPermissions -->
<ng-container *ifPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
<button *ngIf="task.related_document" class="btn btn-sm btn-outline-primary" (click)="dismissAndGo(task); $event.stopPropagation();">
<svg class="sidebaricon" fill="currentColor">
<use xlink:href="assets/bootstrap-icons.svg#file-text"/>
</svg>&nbsp;<ng-container i18n>Open Document</ng-container>
</button>
</div>
</ng-container>
</div>
</td>
</tr>

View File

@ -5,13 +5,17 @@ import { Subject, first } from 'rxjs'
import { PaperlessTask } from 'src/app/data/paperless-task'
import { TasksService } from 'src/app/services/tasks.service'
import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component'
import { ComponentWithPermissions } from '../../with-permissions/with-permissions.component'
@Component({
selector: 'app-tasks',
templateUrl: './tasks.component.html',
styleUrls: ['./tasks.component.scss'],
})
export class TasksComponent implements OnInit, OnDestroy {
export class TasksComponent
extends ComponentWithPermissions
implements OnInit, OnDestroy
{
public activeTab: string
public selectedTasks: Set<number> = new Set()
private unsubscribeNotifer = new Subject()
@ -27,7 +31,9 @@ export class TasksComponent implements OnInit, OnDestroy {
public tasksService: TasksService,
private modalService: NgbModal,
private readonly router: Router
) {}
) {
super()
}
ngOnInit() {
this.tasksService.reload()

View File

@ -0,0 +1,9 @@
import {
PermissionAction,
PermissionType,
} from 'src/app/services/permissions.service'
export class ComponentWithPermissions {
public readonly PermissionAction = PermissionAction
public readonly PermissionType = PermissionType
}

View File

@ -5,31 +5,37 @@ import {
ViewContainerRef,
TemplateRef,
} from '@angular/core'
import { SettingsService } from '../services/settings.service'
import {
PaperlessPermission,
PermissionsService,
} from '../services/permissions.service'
@Directive({
selector: '[ifPermissions]',
})
export class IfPermissionsDirective implements OnInit {
// The role the user must have
@Input() public ifPermissions: Array<string> | string
@Input()
ifPermissions: Array<PaperlessPermission> | PaperlessPermission
/**
* @param {ViewContainerRef} viewContainerRef -- The location where we need to render the templateRef
* @param {TemplateRef<any>} templateRef -- The templateRef to be potentially rendered
* @param {SettignsService} settignsService -- Will give us access to the permissions a user has
* @param {PermissionsService} permissionsService -- Will give us access to the permissions a user has
*/
constructor(
private viewContainerRef: ViewContainerRef,
private templateRef: TemplateRef<any>,
private settingsService: SettingsService
private permissionsService: PermissionsService
) {}
public ngOnInit(): void {
if (
[]
.concat(this.ifPermissions)
.every((perm) => this.settingsService.currentUserCan(perm))
.every((perm: PaperlessPermission) =>
this.permissionsService.currentUserCan(perm)
)
) {
this.viewContainerRef.createEmbeddedView(this.templateRef)
} else {

View File

@ -1,19 +0,0 @@
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
} from '@angular/router'
import { Injectable } from '@angular/core'
import { SettingsService } from '../services/settings.service'
@Injectable()
export class AuthGard implements CanActivate {
constructor(public settingsService: SettingsService) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return this.settingsService.currentUserCan(route.data.requiredPermission)
}
}

View File

@ -0,0 +1,19 @@
import {
CanActivate,
ActivatedRouteSnapshot,
RouterStateSnapshot,
} from '@angular/router'
import { Injectable } from '@angular/core'
import { PermissionsService } from '../services/permissions.service'
@Injectable()
export class PermissionsGuard implements CanActivate {
constructor(private permissionsService: PermissionsService) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): boolean {
return this.permissionsService.currentUserCan(route.data.requiredPermission)
}
}

View File

@ -0,0 +1,49 @@
import { Injectable } from '@angular/core'
export enum PermissionAction {
Add = 'add',
View = 'view',
Change = 'change',
Delete = 'delete',
}
export enum PermissionType {
Document = 'documents.%s_document',
Tag = 'documents.%s_tag',
Correspondent = 'documents.%s_correspondent',
DocumentType = 'documents.%s_documenttype',
StoragePath = 'documents.%s_storagepath',
SavedView = 'documents.%s_savedview',
PaperlessTask = 'documents.%s_paperlesstask',
UISettings = 'documents.%s_uisettings',
Comment = 'documents.%s_comment',
Log = 'admin.%s_logentry',
MailAccount = 'paperless_mail.%s_mailaccount',
MailRule = 'paperless_mail.%s_mailrule',
Auth = 'auth.%s_user',
Admin = 'admin.%s_logentry',
}
export interface PaperlessPermission {
action: PermissionAction
type: PermissionType
}
@Injectable({
providedIn: 'root',
})
export class PermissionsService {
private permissions: string[]
public initialize(permissions: string[]) {
this.permissions = permissions
}
public currentUserCan(permission: PaperlessPermission): boolean {
return this.permissions.includes(this.getPermissionCode(permission))
}
private getPermissionCode(permission: PaperlessPermission): string {
return permission.type.replace('%s', permission.action)
}
}

View File

@ -23,6 +23,7 @@ import {
SETTINGS,
SETTINGS_KEYS,
} from '../data/paperless-uisettings'
import { PermissionsService } from './permissions.service'
import { SavedViewService } from './rest/saved-view.service'
import { ToastService } from './toast.service'
@ -45,7 +46,6 @@ export class SettingsService {
protected baseUrl: string = environment.apiBaseUrl + 'ui_settings/'
private settings: Object = {}
private permissions: string[]
public displayName: string
@ -59,7 +59,8 @@ export class SettingsService {
@Inject(LOCALE_ID) private localeId: string,
protected http: HttpClient,
private toastService: ToastService,
private savedViewService: SavedViewService
private savedViewService: SavedViewService,
private permissionsService: PermissionsService
) {
this.renderer = rendererFactory.createRenderer(null, null)
}
@ -75,7 +76,7 @@ export class SettingsService {
if (this.settings['language']?.length)
this.setLanguage(this.settings['language'])
this.displayName = uisettings.display_name.trim()
this.permissions = uisettings.permissions
this.permissionsService.initialize(uisettings.permissions)
})
)
}
@ -457,8 +458,4 @@ export class SettingsService {
this.savedViewService.dashboardViews.length == 0
)
}
currentUserCan(permission: string): boolean {
return this.permissions.includes(permission)
}
}