mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	feat: reflect django permissions on UI
This commit is contained in:
		| @@ -14,6 +14,7 @@ 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 { DirtyDocGuard } from './guards/dirty-doc.guard' | ||||
| import { DirtySavedViewGuard } from './guards/dirty-saved-view.guard' | ||||
|  | ||||
| @@ -29,25 +30,71 @@ const routes: Routes = [ | ||||
|         path: 'documents', | ||||
|         component: DocumentListComponent, | ||||
|         canDeactivate: [DirtySavedViewGuard], | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_document' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'view/:id', | ||||
|         component: DocumentListComponent, | ||||
|         canDeactivate: [DirtySavedViewGuard], | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_savedview' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'documents/:id', | ||||
|         component: DocumentDetailComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_document' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'asn/:id', | ||||
|         component: DocumentAsnComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_document' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'tags', | ||||
|         component: TagListComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_tag' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'documenttypes', | ||||
|         component: DocumentTypeListComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_documenttype' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'correspondents', | ||||
|         component: CorrespondentListComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_correspondent' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'storagepaths', | ||||
|         component: StoragePathListComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_storagepath' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'logs', | ||||
|         component: LogsComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_log' }, | ||||
|       }, | ||||
|       { path: 'documents/:id', component: DocumentDetailComponent }, | ||||
|       { path: 'asn/:id', component: DocumentAsnComponent }, | ||||
|       { path: 'tags', component: TagListComponent }, | ||||
|       { path: 'documenttypes', component: DocumentTypeListComponent }, | ||||
|       { path: 'correspondents', component: CorrespondentListComponent }, | ||||
|       { path: 'storagepaths', component: StoragePathListComponent }, | ||||
|       { path: 'logs', component: LogsComponent }, | ||||
|       { | ||||
|         path: 'settings', | ||||
|         component: SettingsComponent, | ||||
|         canDeactivate: [DirtyFormGuard], | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_uisettings' }, | ||||
|       }, | ||||
|       { | ||||
|         path: 'tasks', | ||||
|         component: TasksComponent, | ||||
|         canActivate: [AuthGard], | ||||
|         data: { requiredPermission: 'documents.view_paperlesstask' }, | ||||
|       }, | ||||
|       { path: 'tasks', component: TasksComponent }, | ||||
|     ], | ||||
|   }, | ||||
|  | ||||
|   | ||||
| @@ -74,15 +74,27 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|         if ( | ||||
|           this.showNotification(SETTINGS_KEYS.NOTIFICATIONS_CONSUMER_SUCCESS) | ||||
|         ) { | ||||
|           this.toastService.show({ | ||||
|             title: $localize`Document added`, | ||||
|             delay: 10000, | ||||
|             content: $localize`Document ${status.filename} was added to paperless.`, | ||||
|             actionName: $localize`Open document`, | ||||
|             action: () => { | ||||
|               this.router.navigate(['documents', status.documentId]) | ||||
|             }, | ||||
|           // TODO - Is this the only way to allow/disallow from permissions? | ||||
|           var canOpenDocuments = false | ||||
|           this.settings.permissions().subscribe((perm) => { | ||||
|             canOpenDocuments = perm.includes('documents.view_document') | ||||
|           }) | ||||
|           if (canOpenDocuments) | ||||
|             this.toastService.show({ | ||||
|               title: $localize`Document added`, | ||||
|               delay: 10000, | ||||
|               content: $localize`Document ${status.filename} was added to paperless.`, | ||||
|               actionName: $localize`Open document`, | ||||
|               action: () => { | ||||
|                 this.router.navigate(['documents', status.documentId]) | ||||
|               }, | ||||
|             }) | ||||
|           else | ||||
|             this.toastService.show({ | ||||
|               title: $localize`Document added`, | ||||
|               delay: 10000, | ||||
|               content: $localize`Document ${status.filename} was added to paperless.`, | ||||
|             }) | ||||
|         } | ||||
|       }) | ||||
|  | ||||
| @@ -199,7 +211,12 @@ export class AppComponent implements OnInit, OnDestroy { | ||||
|   } | ||||
|  | ||||
|   public get dragDropEnabled(): boolean { | ||||
|     return !this.router.url.includes('dashboard') | ||||
|     // TODO - Is this the only way to allow/disallow from permissions? | ||||
|     var canAddDocuments = false | ||||
|     this.settings.permissions().subscribe((perm) => { | ||||
|       canAddDocuments = perm.includes('documents.add_document') | ||||
|     }) | ||||
|     return !this.router.url.includes('dashboard') && canAddDocuments | ||||
|   } | ||||
|  | ||||
|   public fileOver() { | ||||
|   | ||||
| @@ -41,6 +41,7 @@ import { SelectComponent } from './components/common/input/select/select.compone | ||||
| import { CheckComponent } from './components/common/input/check/check.component' | ||||
| import { SaveViewConfigDialogComponent } from './components/document-list/save-view-config-dialog/save-view-config-dialog.component' | ||||
| import { TagsComponent } from './components/common/input/tags/tags.component' | ||||
| import { IfPermissionsDirective } from './directives/if-permissions.directive' | ||||
| import { SortableDirective } from './directives/sortable.directive' | ||||
| import { CookieService } from 'ngx-cookie-service' | ||||
| import { CsrfInterceptor } from './interceptors/csrf.interceptor' | ||||
| @@ -69,6 +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 { 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' | ||||
| @@ -159,6 +161,7 @@ function initializeApp(settings: SettingsService) { | ||||
|     CheckComponent, | ||||
|     SaveViewConfigDialogComponent, | ||||
|     TagsComponent, | ||||
|     IfPermissionsDirective, | ||||
|     SortableDirective, | ||||
|     SavedViewWidgetComponent, | ||||
|     StatisticsWidgetComponent, | ||||
| @@ -217,6 +220,7 @@ function initializeApp(settings: SettingsService) { | ||||
|     DocumentTitlePipe, | ||||
|     { provide: NgbDateAdapter, useClass: ISODateAdapter }, | ||||
|     { provide: NgbDateParserFormatter, useClass: LocalizedDateParserFormatter }, | ||||
|     AuthGard, | ||||
|     DirtyDocGuard, | ||||
|     DirtySavedViewGuard, | ||||
|   ], | ||||
|   | ||||
| @@ -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"> | ||||
|   <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"]'> | ||||
|     <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()"> | ||||
|         <a ngbDropdownItem class="nav-link" routerLink="settings" (click)="closeMenu()" *ifPermissions='["documents.view_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> <ng-container i18n>Dashboard</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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,79 +80,82 @@ | ||||
|             </a> | ||||
|           </li> | ||||
|         </ul> | ||||
|         <div *ifPermissions='["documents.view_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> | ||||
|           </h6> | ||||
|           <ul class="nav flex-column mb-2"> | ||||
|             <li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews"> | ||||
|               <a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim"> | ||||
|                 <svg class="sidebaricon" fill="currentColor"> | ||||
|                   <use xlink:href="assets/bootstrap-icons.svg#funnel"/> | ||||
|                 </svg><span> {{view.name}}</span> | ||||
|               </a> | ||||
|             </li> | ||||
|           </ul> | ||||
|         </div> | ||||
|  | ||||
|         <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> | ||||
|         </h6> | ||||
|         <ul class="nav flex-column mb-2"> | ||||
|           <li class="nav-item w-100" *ngFor="let view of savedViewService.sidebarViews"> | ||||
|             <a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="view/{{view.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="view.name" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim"> | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#funnel"/> | ||||
|               </svg><span> {{view.name}}</span> | ||||
|             </a> | ||||
|           </li> | ||||
|         </ul> | ||||
|  | ||||
|         <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'> | ||||
|           <span i18n>Open documents</span> | ||||
|         </h6> | ||||
|         <ul class="nav flex-column mb-2"> | ||||
|           <li class="nav-item w-100" *ngFor='let d of openDocuments'> | ||||
|             <a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim"> | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#file-text"/> | ||||
|               </svg><span> {{d.title | documentTitle}}</span> | ||||
|               <span class="close" (click)="closeDocument(d); $event.preventDefault()"> | ||||
|                 <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> | ||||
|         <div *ifPermissions='["documents.view_document"]'> | ||||
|           <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted" *ngIf='openDocuments.length > 0'> | ||||
|             <span i18n>Open documents</span> | ||||
|           </h6> | ||||
|           <ul class="nav flex-column mb-2"> | ||||
|             <li class="nav-item w-100" *ngFor='let d of openDocuments'> | ||||
|               <a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" routerLink="documents/{{d.id}}" routerLinkActive="active" (click)="closeMenu()" [ngbPopover]="d.title | documentTitle" [disablePopover]="!slimSidebarEnabled" placement="end" container="body" triggers="mouseenter:mouseleave" popoverClass="popover-slim"> | ||||
|                 <svg class="sidebaricon" fill="currentColor"> | ||||
|                   <use xlink:href="assets/bootstrap-icons.svg#file-text"/> | ||||
|                 </svg><span> {{d.title | documentTitle}}</span> | ||||
|                 <span class="close" (click)="closeDocument(d); $event.preventDefault()"> | ||||
|                   <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" class="bi bi-x" viewBox="0 0 16 16"> | ||||
|                     <use xlink:href="assets/bootstrap-icons.svg#x"/> | ||||
|                   </svg> | ||||
|                 </span> | ||||
|               </a> | ||||
|             </li> | ||||
|             <li class="nav-item w-100" *ngIf="openDocuments.length >= 1"> | ||||
|               <a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" 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#x"/> | ||||
|                 </svg> | ||||
|               </span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item w-100" *ngIf="openDocuments.length >= 1"> | ||||
|             <a class="nav-link" [class.text-truncate]="!slimSidebarEnabled" [routerLink]="[]" (click)="closeAll()" ngbPopover="Close all" 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#x"/> | ||||
|               </svg><span> <ng-container i18n>Close all</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|         </ul> | ||||
|                 </svg><span> <ng-container i18n>Close all</ng-container></span> | ||||
|               </a> | ||||
|             </li> | ||||
|           </ul> | ||||
|       </div> | ||||
|  | ||||
|         <h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted"> | ||||
|           <span i18n>Manage</span> | ||||
|         </h6> | ||||
|         <ul class="nav flex-column mb-2"> | ||||
|           <li class="nav-item"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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> <ng-container i18n>Correspondents</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item" tourAnchor="tour.tags"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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> <ng-container i18n>Tags</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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> <ng-container i18n>Document types</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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> <ng-container i18n>Storage paths</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item" tourAnchor="tour.file-tasks"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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"> | ||||
| @@ -160,21 +163,21 @@ | ||||
|               </svg><span> <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"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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> <ng-container i18n>Logs</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item" tourAnchor="tour.settings"> | ||||
|           <li class="nav-item" *ifPermissions='["documents.view_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> <ng-container i18n>Settings</ng-container></span> | ||||
|             </a> | ||||
|           </li> | ||||
|           <li class="nav-item" tourAnchor="tour.admin"> | ||||
|           <li class="nav-item" *ifPermissions='["admin.view_logentry"]' 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"/> | ||||
|   | ||||
| @@ -28,12 +28,14 @@ | ||||
|  | ||||
|     <app-welcome-widget *ngIf="settingsService.offerTour()" tourAnchor="tour.dashboard"></app-welcome-widget> | ||||
|  | ||||
|     <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> | ||||
|         <app-saved-view-widget [savedView]="v"></app-saved-view-widget> | ||||
|       </ng-template> | ||||
|     </ng-container> | ||||
|     <div *ifPermissions='["documents.view_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> | ||||
|           <app-saved-view-widget [savedView]="v"></app-saved-view-widget> | ||||
|         </ng-template> | ||||
|       </ng-container> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
|   <div class="col-lg-4"> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <app-widget-frame [title]="savedView.name" [loading]="loading"> | ||||
|  | ||||
|   <a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a> | ||||
|   <a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *ifPermissions='["documents.view_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> | ||||
|     <tbody *ifPermissions='["documents.view_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> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
|     </a> | ||||
|   </div> | ||||
|   <div content tourAnchor="tour.upload-widget"> | ||||
|     <form> | ||||
|     <form *ifPermissions='["documents.add_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,13 +40,15 @@ | ||||
|     <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 *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> | ||||
|         <svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16"> | ||||
|           <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/> | ||||
|         </svg> | ||||
|       </button> | ||||
|     <div *ifPermissions='["documents.view_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> | ||||
|           <svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16"> | ||||
|             <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/> | ||||
|           </svg> | ||||
|         </button> | ||||
|       </div> | ||||
|     </div> | ||||
|   </ngb-alert> | ||||
| </ng-template> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <div *ngIf="comments"> | ||||
|     <form [formGroup]="commentForm" class="needs-validation mt-3" novalidate> | ||||
|     <form [formGroup]="commentForm" class="needs-validation mt-3" *ifPermissions='["documents.add_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)"> | ||||
|             <button type="button" class="btn btn-link btn-sm p-0 fade" (click)="deleteComment(comment.id)" *ifPermissions='["documents.delete_comment"]'> | ||||
|                 <svg width="13" height="13" fill="currentColor"> | ||||
|                     <use xlink:href="assets/bootstrap-icons.svg#trash" /> | ||||
|                 </svg> | ||||
|   | ||||
| @@ -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()"> | ||||
|     <button type="button" class="btn btn-sm btn-outline-danger me-2 ms-auto" (click)="delete()" *ifPermissions='["documents.delete_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>  | ||||
|             <button type="button" class="btn btn-outline-primary" (click)="saveEditNext()" *ngIf="hasNext()" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save & next</button>  | ||||
|             <button type="submit" class="btn btn-primary" i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>  | ||||
|             <button type="submit" class="btn btn-primary" *ifPermissions='["documents.change_document"]' i18n [disabled]="networkActive || !(isDirty$ | async) || error">Save</button>  | ||||
|         </form> | ||||
|     </div> | ||||
|  | ||||
|   | ||||
| @@ -553,6 +553,11 @@ export class DocumentDetailComponent | ||||
|   } | ||||
|  | ||||
|   get commentsEnabled(): boolean { | ||||
|     return this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) | ||||
|     // TODO - Is this the only way to allow/disallow from permissions? | ||||
|     var canViewComments = false | ||||
|     this.settings.permissions().subscribe((perm) => { | ||||
|       canViewComments = perm.includes('documents.view_comment') | ||||
|     }) | ||||
|     return this.settings.get(SETTINGS_KEYS.COMMENTS_ENABLED) && canViewComments | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -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"> | ||||
|     <div class="d-flex" *ifPermissions='["documents.change_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()"> | ||||
|     <button type="button" class="btn btn-sm btn-outline-danger" (click)="applyDelete()" *ifPermissions='["documents.delete_document"]'> | ||||
|       <svg width="1em" height="1em" viewBox="0 0 16 16" fill="currentColor"> | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#trash" /> | ||||
|       </svg> <ng-container i18n>Delete</ng-container> | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#diagram-3"/> | ||||
|               </svg> <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"> | ||||
|             <a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" *ifPermissions='["documents.change_document"]'> | ||||
|               <svg class="sidebaricon" fill="currentColor" class="sidebaricon"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#pencil"/> | ||||
|               </svg> <span class="d-none d-md-inline" i18n>Edit</span> | ||||
|   | ||||
| @@ -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" i18n-title> | ||||
|           <a (click)="openDocumentsService.openDocument(document)" class="btn btn-sm btn-outline-secondary" title="Edit" *ifPermissions='["documents.change_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> | ||||
|   | ||||
| @@ -59,7 +59,7 @@ | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   <div class="btn-group ms-2 flex-fill" ngbDropdown role="group"> | ||||
|   <div class="btn-group ms-2 flex-fill" *ifPermissions='["documents.view_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,8 +72,10 @@ | ||||
|         <div class="dropdown-divider" *ngIf="savedViewService.allViews.length > 0"></div> | ||||
|       </ng-container> | ||||
|  | ||||
|       <button ngbDropdownItem (click)="saveViewConfig()" *ngIf="list.activeSavedViewId" [disabled]="!savedViewIsModified" i18n>Save "{{list.activeSavedViewTitle}}"</button> | ||||
|       <button ngbDropdownItem (click)="saveViewConfigAs()" i18n>Save as...</button> | ||||
|       <div *ifPermissions='["documents.change_savedviewfilterrule"]'> | ||||
|         <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> | ||||
|     </div> | ||||
|   </div> | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <app-page-header title="{{ typeNamePlural | titlecase }}"> | ||||
|   <button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" i18n>Create</button> | ||||
|   <button type="button" class="btn btn-sm btn-outline-primary" (click)="openCreateDialog()" *ifPermissions='["documents.add_" + typeNameWithoutWhitespace()]' i18n>Create</button> | ||||
| </app-page-header> | ||||
|  | ||||
| <div class="row"> | ||||
| @@ -41,24 +41,24 @@ | ||||
|               </svg> | ||||
|             </button> | ||||
|             <div ngbDropdownMenu aria-labelledby="actionsMenuMobile"> | ||||
|               <button (click)="filterDocuments(object)" ngbDropdownItem i18n>Filter Documents</button> | ||||
|               <button (click)="openEditDialog(object)" ngbDropdownItem i18n>Edit</button> | ||||
|               <button class="text-danger" (click)="openDeleteDialog(object)" ngbDropdownItem i18n>Delete</button> | ||||
|               <button (click)="filterDocuments(object)" *ifPermissions='["documents.view_document"]' ngbDropdownItem i18n>Filter Documents</button> | ||||
|               <button (click)="openEditDialog(object)" *ifPermissions='["documents.change_" + typeNameWithoutWhitespace()]' ngbDropdownItem i18n>Edit</button> | ||||
|               <button class="text-danger" (click)="openDeleteDialog(object)" *ifPermissions='["documents.delete_" + typeNameWithoutWhitespace()]' 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)"> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="filterDocuments(object)" *ifPermissions='["documents.view_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> <ng-container i18n>Documents</ng-container> | ||||
|           </button> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)"> | ||||
|           <button class="btn btn-sm btn-outline-secondary" (click)="openEditDialog(object)" *ifPermissions='["documents.change_" + typeNameWithoutWhitespace()]'> | ||||
|             <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> <ng-container i18n>Edit</ng-container> | ||||
|           </button> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)"> | ||||
|           <button class="btn btn-sm btn-outline-danger" (click)="openDeleteDialog(object)" *ifPermissions='["documents.delete_" + typeNameWithoutWhitespace()]'> | ||||
|             <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"/> | ||||
|   | ||||
| @@ -60,6 +60,12 @@ export abstract class ManagementListComponent<T extends ObjectWithId> | ||||
|   public sortField: string | ||||
|   public sortReverse: boolean | ||||
|  | ||||
|   // TODO - Getter used to automatically build a permission name from typeName | ||||
|   // Will basically break if permission name is different than typeName | ||||
|   public typeNameWithoutWhitespace(): string { | ||||
|     return this.typeName.replace(/\s/g, '') | ||||
|   } | ||||
|  | ||||
|   private nameFilterDebounce: Subject<string> | ||||
|   private subscription: Subscription | ||||
|   private _nameFilter: string | ||||
|   | ||||
| @@ -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)" i18n>Delete</button> | ||||
|                 <button type="button" class="btn btn-sm btn-outline-danger form-control" (click)="deleteSavedView(view)" *ifPermissions='["documents.delete_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" [disabled]="!(isDirty$ | async)" i18n>Save</button> | ||||
|   <button type="submit" class="btn btn-primary mb-2" *ifPermissions='["documents.change_uisettings"]' [disabled]="!(isDirty$ | async)" i18n>Save</button> | ||||
| </form> | ||||
|   | ||||
| @@ -5,7 +5,7 @@ | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#x"/> | ||||
|       </svg> <ng-container i18n>Clear selection</ng-container> | ||||
|     </button> | ||||
|     <button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" [disabled]="tasksService.total == 0"> | ||||
|     <button class="btn btn-sm btn-outline-primary me-4" (click)="dismissTasks()" *ifPermissions='["django_q.delete_task"]' [disabled]="tasksService.total == 0"> | ||||
|       <svg class="sidebaricon" fill="currentColor"> | ||||
|         <use xlink:href="assets/bootstrap-icons.svg#check2-all"/> | ||||
|       </svg> <ng-container i18n>{{dismissButtonText}}</ng-container> | ||||
| @@ -75,16 +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();"> | ||||
|             <button class="btn btn-sm btn-outline-secondary" (click)="dismissTask(task); $event.stopPropagation();" *ifPermissions='["django_q.delete_task"]'> | ||||
|               <svg class="sidebaricon" fill="currentColor"> | ||||
|                 <use xlink:href="assets/bootstrap-icons.svg#check"/> | ||||
|               </svg> <ng-container i18n>Dismiss</ng-container> | ||||
|             </button> | ||||
|             <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> <ng-container i18n>Open Document</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 --> | ||||
|               <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> <ng-container i18n>Open Document</ng-container> | ||||
|               </button> | ||||
|             </div> | ||||
|           </div> | ||||
|         </td> | ||||
|       </tr> | ||||
|   | ||||
| @@ -1,11 +1,9 @@ | ||||
| export interface PaperlessUiSettings { | ||||
|   user_id: number | ||||
|  | ||||
|   username: string | ||||
|  | ||||
|   display_name: string | ||||
|  | ||||
|   settings: Object | ||||
|   permissions: string[] | ||||
| } | ||||
|  | ||||
| export interface PaperlessUiSetting { | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| import { IfPermissionsDirective } from './if-permissions.directive' | ||||
|  | ||||
| // TODO - Must be implemented | ||||
| describe('IfPermissionsDirective', () => { | ||||
|   it('should create an instance', () => { | ||||
|     const directive = new IfPermissionsDirective() | ||||
|     expect(directive).toBeTruthy() | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										60
									
								
								src-ui/src/app/directives/if-permissions.directive.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src-ui/src/app/directives/if-permissions.directive.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| import { | ||||
|   Input, | ||||
|   OnInit, | ||||
|   Directive, | ||||
|   ViewContainerRef, | ||||
|   TemplateRef, | ||||
|   OnDestroy, | ||||
| } from '@angular/core' | ||||
| import { Subscription } from 'rxjs' | ||||
| import { SettingsService } from '../services/settings.service' | ||||
|  | ||||
| @Directive({ | ||||
|   selector: '[ifPermissions]', | ||||
| }) | ||||
| export class IfPermissionsDirective implements OnInit, OnDestroy { | ||||
|   private subscription: Subscription[] = [] | ||||
|   // The role the user must have | ||||
|   @Input() public ifPermissions: Array<string> | ||||
|  | ||||
|   /** | ||||
|    * @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 | ||||
|    */ | ||||
|   constructor( | ||||
|     private viewContainerRef: ViewContainerRef, | ||||
|     private templateRef: TemplateRef<any>, | ||||
|     private settingsService: SettingsService | ||||
|   ) {} | ||||
|  | ||||
|   public ngOnInit(): void { | ||||
|     this.subscription.push( | ||||
|       this.settingsService.permissions().subscribe((permission) => { | ||||
|         if (!permission) { | ||||
|           // Remove element from DOM | ||||
|           this.viewContainerRef.clear() | ||||
|         } | ||||
|         // User permissions are checked by a permission mention in DOM | ||||
|         const idx = permission.findIndex( | ||||
|           (element) => this.ifPermissions.indexOf(element) !== -1 | ||||
|         ) | ||||
|         if (idx < 0) { | ||||
|           this.viewContainerRef.clear() | ||||
|         } else { | ||||
|           // Appends the ref element to DOM | ||||
|           this.viewContainerRef.createEmbeddedView(this.templateRef) | ||||
|         } | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * On destroy cancels the API if its fetching. | ||||
|    */ | ||||
|   public ngOnDestroy(): void { | ||||
|     this.subscription.forEach((subscription: Subscription) => | ||||
|       subscription.unsubscribe() | ||||
|     ) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										23
									
								
								src-ui/src/app/guards/auth.gard.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src-ui/src/app/guards/auth.gard.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| 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 { | ||||
|     var canActivate = false | ||||
|     this.settingsService.permissions().subscribe((perm) => { | ||||
|       canActivate = perm.includes(route.data.requiredPermission) | ||||
|     }) | ||||
|     return canActivate | ||||
|   } | ||||
| } | ||||
| @@ -11,7 +11,7 @@ import { | ||||
| } from '@angular/core' | ||||
| import { Meta } from '@angular/platform-browser' | ||||
| import { CookieService } from 'ngx-cookie-service' | ||||
| import { first, Observable, tap } from 'rxjs' | ||||
| import { first, Observable, of, tap } from 'rxjs' | ||||
| import { | ||||
|   BRIGHTNESS, | ||||
|   estimateBrightnessForColor, | ||||
| @@ -45,6 +45,7 @@ export class SettingsService { | ||||
|   protected baseUrl: string = environment.apiBaseUrl + 'ui_settings/' | ||||
|  | ||||
|   private settings: Object = {} | ||||
|   private _permissions: string[] | ||||
|  | ||||
|   public displayName: string | ||||
|  | ||||
| @@ -74,6 +75,7 @@ export class SettingsService { | ||||
|         if (this.settings['language']?.length) | ||||
|           this.setLanguage(this.settings['language']) | ||||
|         this.displayName = uisettings.display_name.trim() | ||||
|         this._permissions = uisettings.permissions | ||||
|       }) | ||||
|     ) | ||||
|   } | ||||
| @@ -455,4 +457,8 @@ export class SettingsService { | ||||
|       this.savedViewService.dashboardViews.length == 0 | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   public permissions(): Observable<string[]> { | ||||
|     return of(this._permissions) | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Kaaybi
					Kaaybi