From 06c63ef4a4445fd967d68be1251236d9cbe99f12 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Thu, 17 Aug 2023 20:09:40 -0700 Subject: [PATCH] Disable / hide some UI buttons / elements if insufficient permissions --- src-ui/messages.xlf | 112 +++++++----- .../manage/settings/settings.component.html | 26 +-- .../settings/settings.component.spec.ts | 160 ++++++++++++++---- .../manage/settings/settings.component.ts | 74 ++++++-- src-ui/src/app/services/toast.service.ts | 2 +- src-ui/src/theme.scss | 3 + 6 files changed, 272 insertions(+), 105 deletions(-) diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 865591166..1d07e98e4 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -723,7 +723,7 @@ src/app/components/manage/settings/settings.component.ts - 600 + 648 @@ -2913,19 +2913,19 @@ src/app/components/manage/settings/settings.component.ts - 711 + 759 src/app/components/manage/settings/settings.component.ts - 771 + 819 src/app/components/manage/settings/settings.component.ts - 838 + 886 src/app/components/manage/settings/settings.component.ts - 901 + 949 @@ -2940,19 +2940,19 @@ src/app/components/manage/settings/settings.component.ts - 713 + 761 src/app/components/manage/settings/settings.component.ts - 773 + 821 src/app/components/manage/settings/settings.component.ts - 840 + 888 src/app/components/manage/settings/settings.component.ts - 903 + 951 @@ -4489,235 +4489,263 @@ 372 + + Error retrieving groups + + src/app/components/manage/settings/settings.component.ts + 278 + + + + Error retrieving users + + src/app/components/manage/settings/settings.component.ts + 287 + + + + Error retrieving mail rules + + src/app/components/manage/settings/settings.component.ts + 314 + + + + Error retrieving mail accounts + + src/app/components/manage/settings/settings.component.ts + 323 + + Saved view "" deleted. src/app/components/manage/settings/settings.component.ts - 482 + 530 Settings saved src/app/components/manage/settings/settings.component.ts - 584 + 632 Settings were saved successfully. src/app/components/manage/settings/settings.component.ts - 585 + 633 Settings were saved successfully. Reload is required to apply some changes. src/app/components/manage/settings/settings.component.ts - 589 + 637 Reload now src/app/components/manage/settings/settings.component.ts - 590 + 638 Use system language src/app/components/manage/settings/settings.component.ts - 609 + 657 Use date format of display language src/app/components/manage/settings/settings.component.ts - 616 + 664 Error while storing settings on server. src/app/components/manage/settings/settings.component.ts - 636 + 684 Password has been changed, you will be logged out momentarily. src/app/components/manage/settings/settings.component.ts - 679 + 727 Saved user "". src/app/components/manage/settings/settings.component.ts - 686 + 734 Error saving user. src/app/components/manage/settings/settings.component.ts - 698 + 746 Confirm delete user account src/app/components/manage/settings/settings.component.ts - 709 + 757 This operation will permanently delete this user account. src/app/components/manage/settings/settings.component.ts - 710 + 758 Deleted user src/app/components/manage/settings/settings.component.ts - 719 + 767 Error deleting user. src/app/components/manage/settings/settings.component.ts - 727 + 775 Saved group "". src/app/components/manage/settings/settings.component.ts - 748 + 796 Error saving group. src/app/components/manage/settings/settings.component.ts - 758 + 806 Confirm delete user group src/app/components/manage/settings/settings.component.ts - 769 + 817 This operation will permanently delete this user group. src/app/components/manage/settings/settings.component.ts - 770 + 818 Deleted group src/app/components/manage/settings/settings.component.ts - 779 + 827 Error deleting group. src/app/components/manage/settings/settings.component.ts - 787 + 835 Saved account "". src/app/components/manage/settings/settings.component.ts - 813 + 861 Error saving account. src/app/components/manage/settings/settings.component.ts - 825 + 873 Confirm delete mail account src/app/components/manage/settings/settings.component.ts - 836 + 884 This operation will permanently delete this mail account. src/app/components/manage/settings/settings.component.ts - 837 + 885 Deleted mail account src/app/components/manage/settings/settings.component.ts - 846 + 894 Error deleting mail account. src/app/components/manage/settings/settings.component.ts - 855 + 903 Saved rule "". src/app/components/manage/settings/settings.component.ts - 876 + 924 Error saving rule. src/app/components/manage/settings/settings.component.ts - 888 + 936 Confirm delete mail rule src/app/components/manage/settings/settings.component.ts - 899 + 947 This operation will permanently delete this mail rule. src/app/components/manage/settings/settings.component.ts - 900 + 948 Deleted mail rule src/app/components/manage/settings/settings.component.ts - 909 + 957 Error deleting mail rule. src/app/components/manage/settings/settings.component.ts - 918 + 966 diff --git a/src-ui/src/app/components/manage/settings/settings.component.html b/src-ui/src/app/components/manage/settings/settings.component.html index 5090d531d..8b0132902 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.html +++ b/src-ui/src/app/components/manage/settings/settings.component.html @@ -243,7 +243,7 @@ Mail accounts - + @@ -262,7 +262,7 @@ - {{account.name}} + {{account.name}} {{account.imap_server}} @@ -280,7 +280,7 @@ Mail rules - + @@ -299,7 +299,7 @@ - {{rule.name}} + {{rule.name}} {{(mailAccountService.getCached(rule.account) | async)?.name}} @@ -323,7 +323,7 @@ - + Users & Groups @@ -334,7 +334,7 @@ - Add User + Add User @@ -350,13 +350,13 @@ - {{user.username}} + {{user.username}} {{user.first_name}} {{user.last_name}} {{user.groups?.map(getGroupName, this).join(', ')}} - Edit - Delete + Edit + Delete @@ -369,7 +369,7 @@ - Add Group + Add Group 0" class="list-group" formGroupName="groupsGroup"> @@ -385,13 +385,13 @@ - {{group.name}} + {{group.name}} - Edit - Delete + Edit + Delete diff --git a/src-ui/src/app/components/manage/settings/settings.component.spec.ts b/src-ui/src/app/components/manage/settings/settings.component.spec.ts index c4a9d4a4b..3919e6499 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.spec.ts +++ b/src-ui/src/app/components/manage/settings/settings.component.spec.ts @@ -116,52 +116,66 @@ describe('SettingsComponent', () => { jest .spyOn(permissionsService, 'currentUserOwnsObject') .mockReturnValue(true) - jest.spyOn(userService, 'listAll').mockReturnValue( - of({ - all: users.map((u) => u.id), - count: users.length, - results: users.concat([]), - }) - ) groupService = TestBed.inject(GroupService) - jest.spyOn(groupService, 'listAll').mockReturnValue( - of({ - all: groups.map((g) => g.id), - count: groups.length, - results: groups.concat([]), - }) - ) savedViewService = TestBed.inject(SavedViewService) - jest.spyOn(savedViewService, 'listAll').mockReturnValue( - of({ - all: savedViews.map((v) => v.id), - count: savedViews.length, - results: (savedViews as PaperlessSavedView[]).concat([]), - }) - ) mailAccountService = TestBed.inject(MailAccountService) - jest.spyOn(mailAccountService, 'listAll').mockReturnValue( - of({ - all: mailAccounts.map((a) => a.id), - count: mailAccounts.length, - results: (mailAccounts as PaperlessMailAccount[]).concat([]), - }) - ) mailRuleService = TestBed.inject(MailRuleService) - jest.spyOn(mailRuleService, 'listAll').mockReturnValue( - of({ - all: mailRules.map((r) => r.id), - count: mailRules.length, - results: (mailRules as PaperlessMailRule[]).concat([]), - }) - ) + }) + + function completeSetup(excludeService = null) { + if (excludeService !== userService) { + jest.spyOn(userService, 'listAll').mockReturnValue( + of({ + all: users.map((u) => u.id), + count: users.length, + results: users.concat([]), + }) + ) + } + if (excludeService !== groupService) { + jest.spyOn(groupService, 'listAll').mockReturnValue( + of({ + all: groups.map((g) => g.id), + count: groups.length, + results: groups.concat([]), + }) + ) + } + if (excludeService !== savedViewService) { + jest.spyOn(savedViewService, 'listAll').mockReturnValue( + of({ + all: savedViews.map((v) => v.id), + count: savedViews.length, + results: (savedViews as PaperlessSavedView[]).concat([]), + }) + ) + } + if (excludeService !== mailAccountService) { + jest.spyOn(mailAccountService, 'listAll').mockReturnValue( + of({ + all: mailAccounts.map((a) => a.id), + count: mailAccounts.length, + results: (mailAccounts as PaperlessMailAccount[]).concat([]), + }) + ) + } + if (excludeService !== mailRuleService) { + jest.spyOn(mailRuleService, 'listAll').mockReturnValue( + of({ + all: mailRules.map((r) => r.id), + count: mailRules.length, + results: (mailRules as PaperlessMailRule[]).concat([]), + }) + ) + } fixture = TestBed.createComponent(SettingsComponent) component = fixture.componentInstance fixture.detectChanges() - }) + } it('should support tabbed settings & change URL, prevent navigation if dirty confirmation rejected', () => { + completeSetup() const navigateSpy = jest.spyOn(router, 'navigate') const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) tabButtons[1].nativeElement.dispatchEvent(new MouseEvent('click')) @@ -187,6 +201,7 @@ describe('SettingsComponent', () => { }) it('should support direct link to tab by URL, scroll if needed', () => { + completeSetup() jest .spyOn(activatedRoute, 'paramMap', 'get') .mockReturnValue(of(convertToParamMap({ section: 'mail' }))) @@ -199,6 +214,7 @@ describe('SettingsComponent', () => { }) it('should lazy load tab data', () => { + completeSetup() const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) expect(component.savedViews).toBeUndefined() @@ -221,6 +237,7 @@ describe('SettingsComponent', () => { }) it('should support save saved views, show error', () => { + completeSetup() component.maybeInitializeTab(3) // SavedViews const toastErrorSpy = jest.spyOn(toastService, 'showError') @@ -248,6 +265,7 @@ describe('SettingsComponent', () => { }) it('should support save local settings updating appearance settings and calling API, show error', () => { + completeSetup() const toastErrorSpy = jest.spyOn(toastService, 'showError') const toastSpy = jest.spyOn(toastService, 'show') const storeSpy = jest.spyOn(settingsService, 'storeSettings') @@ -275,6 +293,7 @@ describe('SettingsComponent', () => { }) it('should offer reload if settings changes require', () => { + completeSetup() let toast: Toast toastService.getToasts().subscribe((t) => (toast = t[0])) component.initialize(true) // reset @@ -288,6 +307,7 @@ describe('SettingsComponent', () => { }) it('should allow setting theme color, visually apply change immediately but not save', () => { + completeSetup() const appearanceSpy = jest.spyOn( settingsService, 'updateAppearanceSettings' @@ -304,6 +324,7 @@ describe('SettingsComponent', () => { }) it('should support delete saved view', () => { + completeSetup() component.maybeInitializeTab(3) // SavedViews const toastSpy = jest.spyOn(toastService, 'showInfo') const deleteSpy = jest.spyOn(savedViewService, 'delete') @@ -316,6 +337,7 @@ describe('SettingsComponent', () => { }) it('should support edit / create user, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.editUser(users[0]) @@ -332,6 +354,7 @@ describe('SettingsComponent', () => { }) it('should support delete user, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.deleteUser(users[0]) @@ -352,6 +375,7 @@ describe('SettingsComponent', () => { }) it('should logout current user if password changed, after delay', fakeAsync(() => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.editUser(users[0]) @@ -371,6 +395,7 @@ describe('SettingsComponent', () => { })) it('should support edit / create group, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.editGroup(groups[0]) @@ -386,6 +411,7 @@ describe('SettingsComponent', () => { }) it('should support delete group, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.deleteGroup(users[0]) @@ -406,12 +432,71 @@ describe('SettingsComponent', () => { }) it('should get group name', () => { + completeSetup() component.maybeInitializeTab(5) // UsersGroups expect(component.getGroupName(1)).toEqual(groups[0].name) expect(component.getGroupName(11)).toEqual('') }) + it('should show errors on load if load mailAccounts failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(mailAccountService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load mail accounts')) + ) + completeSetup(mailAccountService) + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) // mail tab + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) + + it('should show errors on load if load mailRules failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(mailRuleService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load mail rules')) + ) + completeSetup(mailRuleService) + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[3].nativeElement.dispatchEvent(new MouseEvent('click')) // mail tab + fixture.detectChanges() + // tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click')) + expect(toastErrorSpy).toBeCalled() + }) + + it('should show errors on load if load users failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(userService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load users')) + ) + completeSetup(userService) + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click')) // users tab + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) + + it('should show errors on load if load groups failure', () => { + const toastErrorSpy = jest.spyOn(toastService, 'showError') + jest + .spyOn(groupService, 'listAll') + .mockImplementation(() => + throwError(() => new Error('failed to load groups')) + ) + completeSetup(groupService) + const tabButtons = fixture.debugElement.queryAll(By.directive(NgbNavLink)) + tabButtons[4].nativeElement.dispatchEvent(new MouseEvent('click')) // users tab + fixture.detectChanges() + expect(toastErrorSpy).toBeCalled() + }) + it('should support edit / create mail account, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.editMailAccount(mailAccounts[0] as PaperlessMailAccount) @@ -427,6 +512,7 @@ describe('SettingsComponent', () => { }) it('should support delete mail account, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.deleteMailAccount(mailAccounts[0] as PaperlessMailAccount) @@ -447,6 +533,7 @@ describe('SettingsComponent', () => { }) it('should support edit / create mail rule, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.editMailRule(mailRules[0] as PaperlessMailRule) @@ -462,6 +549,7 @@ describe('SettingsComponent', () => { }) it('should support delete mail rule, show error if needed', () => { + completeSetup() let modal: NgbModalRef modalService.activeInstances.subscribe((refs) => (modal = refs[0])) component.deleteMailRule(mailRules[0] as PaperlessMailRule) diff --git a/src-ui/src/app/components/manage/settings/settings.component.ts b/src-ui/src/app/components/manage/settings/settings.component.ts index a49f2dd21..785e5d347 100644 --- a/src-ui/src/app/components/manage/settings/settings.component.ts +++ b/src-ui/src/app/components/manage/settings/settings.component.ts @@ -146,7 +146,7 @@ export class SettingsComponent private groupsService: GroupService, private router: Router, private modalService: NgbModal, - private permissionsService: PermissionsService + public permissionsService: PermissionsService ) { super() this.settings.settingsSaved.subscribe(() => { @@ -259,25 +259,73 @@ export class SettingsComponent navID == SettingsNavIDs.UsersGroups && (!this.users || !this.groups) ) { - this.usersService.listAll().subscribe((r) => { - this.users = r.results - this.groupsService.listAll().subscribe((r) => { - this.groups = r.results - this.initialize(false) + this.usersService + .listAll() + .pipe(first()) + .subscribe({ + next: (r) => { + this.users = r.results + this.groupsService + .listAll() + .pipe(first()) + .subscribe({ + next: (r) => { + this.groups = r.results + this.initialize(false) + }, + error: (e) => { + this.toastService.showError( + $localize`Error retrieving groups`, + 10000, + JSON.stringify(e) + ) + }, + }) + }, + error: (e) => { + this.toastService.showError( + $localize`Error retrieving users`, + 10000, + JSON.stringify(e) + ) + }, }) - }) } else if ( navID == SettingsNavIDs.Mail && (!this.mailAccounts || !this.mailRules) ) { - this.mailAccountService.listAll().subscribe((r) => { - this.mailAccounts = r.results + this.mailAccountService + .listAll() + .pipe(first()) + .subscribe({ + next: (r) => { + this.mailAccounts = r.results - this.mailRuleService.listAll().subscribe((r) => { - this.mailRules = r.results - this.initialize(false) + this.mailRuleService + .listAll() + .pipe(first()) + .subscribe({ + next: (r) => { + this.mailRules = r.results + this.initialize(false) + }, + error: (e) => { + this.toastService.showError( + $localize`Error retrieving mail rules`, + 10000, + JSON.stringify(e) + ) + }, + }) + }, + error: (e) => { + this.toastService.showError( + $localize`Error retrieving mail accounts`, + 10000, + JSON.stringify(e) + ) + }, }) - }) } } diff --git a/src-ui/src/app/services/toast.service.ts b/src-ui/src/app/services/toast.service.ts index 2d11d663e..ef282c522 100644 --- a/src-ui/src/app/services/toast.service.ts +++ b/src-ui/src/app/services/toast.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core' -import { Subject, zip } from 'rxjs' +import { Subject } from 'rxjs' export interface Toast { title: string diff --git a/src-ui/src/theme.scss b/src-ui/src/theme.scss index d90afa6c1..33748c81b 100644 --- a/src-ui/src/theme.scss +++ b/src-ui/src/theme.scss @@ -80,6 +80,9 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,