diff --git a/.github/workflows/cleanup-tags.yml b/.github/workflows/cleanup-tags.yml index 5a5085775..e0d81bb4a 100644 --- a/.github/workflows/cleanup-tags.yml +++ b/.github/workflows/cleanup-tags.yml @@ -6,10 +6,9 @@ # This workflow will not trigger runs on forked repos. name: Cleanup Image Tags on: - delete: - push: - paths: - - ".github/workflows/cleanup-tags.yml" + workflow_dispatch: + schedule: + - cron: '0 0 * * 0' concurrency: group: registry-tags-cleanup cancel-in-progress: false diff --git a/docs/configuration.md b/docs/configuration.md index 20d964ddc..5d6673fab 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -170,11 +170,11 @@ Available options are `postgresql` and `mariadb`. !!! note - A small pool is typically sufficient — for example, a size of 4. - Make sure your PostgreSQL server's max_connections setting is large enough to handle: - ```(Paperless workers + Celery workers) × pool size + safety margin``` - For example, with 4 Paperless workers and 2 Celery workers, and a pool size of 4: - (4 + 2) × 4 + 10 = 34 connections required. + A small pool is typically sufficient — for example, a size of 4. + Make sure your PostgreSQL server's max_connections setting is large enough to handle: + ```(Paperless workers + Celery workers) × pool size + safety margin``` + For example, with 4 Paperless workers and 2 Celery workers, and a pool size of 4: + (4 + 2) × 4 + 10 = 34 connections required. #### [`PAPERLESS_DB_READ_CACHE_ENABLED=`](#PAPERLESS_DB_READ_CACHE_ENABLED) {#PAPERLESS_DB_READ_CACHE_ENABLED} @@ -184,9 +184,9 @@ Available options are `postgresql` and `mariadb`. !!! danger - **Do not modify the database outside the application while it is running.** - This includes actions such as restoring a backup, upgrading the database, or performing manual inserts. All external modifications must be done **only when the application is stopped**. - After making any such changes, you **must invalidate the DB read cache** using the `invalidate_cachalot` management command. + **Do not modify the database outside the application while it is running.** + This includes actions such as restoring a backup, upgrading the database, or performing manual inserts. All external modifications must be done **only when the application is stopped**. + After making any such changes, you **must invalidate the DB read cache** using the `invalidate_cachalot` management command. #### [`PAPERLESS_READ_CACHE_TTL=`](#PAPERLESS_READ_CACHE_TTL) {#PAPERLESS_READ_CACHE_TTL} @@ -196,7 +196,7 @@ Available options are `postgresql` and `mariadb`. !!! warning - A high TTL increases memory usage over time. Memory may be used until end of TTL, even if the cache is invalidated with the `invalidate_cachalot` command. + A high TTL increases memory usage over time. Memory may be used until end of TTL, even if the cache is invalidated with the `invalidate_cachalot` command. In case of an out-of-memory (OOM) situation, Redis may stop accepting new data — including cache entries, scheduled tasks, and documents to consume. If your system has limited RAM, consider configuring a dedicated Redis instance for the read cache, with a memory limit and the eviction policy set to `allkeys-lru`. diff --git a/docs/usage.md b/docs/usage.md index a3a2f8901..8d855cadd 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -414,7 +414,7 @@ fields and permissions, which will be merged. #### Types {#workflow-trigger-types} -Currently, there are three events that correspond to workflow trigger 'types': +Currently, there are four events that correspond to workflow trigger 'types': 1. **Consumption Started**: _before_ a document is consumed, so events can include filters by source (mail, consumption folder or API), file path, file name, mail rule @@ -427,7 +427,7 @@ Currently, there are three events that correspond to workflow trigger 'types': added, created, updated date or you can specify a (date) custom field. You can also specify a day offset from the date (positive offsets will trigger after the date, negative offsets will trigger before). -The following flow diagram illustrates the three document trigger types: +The following flow diagram illustrates the four document trigger types: ```mermaid flowchart TD diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 77c82e829..7fcf7976b 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -1636,7 +1636,7 @@ src/app/components/admin/tasks/tasks.component.ts - 44 + 45 src/app/components/admin/trash/trash.component.html @@ -1862,7 +1862,7 @@ src/app/components/admin/tasks/tasks.component.ts - 153 + 155 @@ -1918,63 +1918,77 @@ Result src/app/components/admin/tasks/tasks.component.ts - 45 + 46 Dismiss selected src/app/components/admin/tasks/tasks.component.ts - 108 + 110 Dismiss all src/app/components/admin/tasks/tasks.component.ts - 109 + 111 Confirm Dismiss All src/app/components/admin/tasks/tasks.component.ts - 150 + 152 Dismiss all tasks? src/app/components/admin/tasks/tasks.component.ts - 151 + 153 + + + + Error dismissing tasks + + src/app/components/admin/tasks/tasks.component.ts + 161 + + + + Error dismissing task + + src/app/components/admin/tasks/tasks.component.ts + 170 queued src/app/components/admin/tasks/tasks.component.ts - 236 + 246 started src/app/components/admin/tasks/tasks.component.ts - 238 + 248 completed src/app/components/admin/tasks/tasks.component.ts - 240 + 250 failed src/app/components/admin/tasks/tasks.component.ts - 242 + 252 @@ -2560,11 +2574,11 @@ src/app/components/document-detail/document-detail.component.ts - 1025 + 1028 src/app/components/document-detail/document-detail.component.ts - 1390 + 1393 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -3176,7 +3190,7 @@ src/app/components/document-detail/document-detail.component.ts - 978 + 981 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -6654,7 +6668,7 @@ src/app/components/document-detail/document-detail.component.ts - 1389 + 1392 @@ -7018,53 +7032,53 @@ Error retrieving metadata src/app/components/document-detail/document-detail.component.ts - 666 + 669 Error retrieving suggestions. src/app/components/document-detail/document-detail.component.ts - 695 + 698 Document "" saved successfully. src/app/components/document-detail/document-detail.component.ts - 867 + 870 src/app/components/document-detail/document-detail.component.ts - 891 + 894 Error saving document "" src/app/components/document-detail/document-detail.component.ts - 897 + 900 Error saving document src/app/components/document-detail/document-detail.component.ts - 947 + 950 Do you really want to move the document "" to the trash? src/app/components/document-detail/document-detail.component.ts - 979 + 982 Documents can be restored prior to permanent deletion. src/app/components/document-detail/document-detail.component.ts - 980 + 983 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -7075,7 +7089,7 @@ Move to trash src/app/components/document-detail/document-detail.component.ts - 982 + 985 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -7086,14 +7100,14 @@ Error deleting document src/app/components/document-detail/document-detail.component.ts - 1001 + 1004 Reprocess confirm src/app/components/document-detail/document-detail.component.ts - 1021 + 1024 src/app/components/document-list/bulk-editor/bulk-editor.component.ts @@ -7104,81 +7118,81 @@ This operation will permanently recreate the archive file for this document. src/app/components/document-detail/document-detail.component.ts - 1022 + 1025 The archive file will be re-generated with the current settings. src/app/components/document-detail/document-detail.component.ts - 1023 + 1026 Reprocess operation for "" will begin in the background. Close and re-open or reload this document after the operation has completed to see new content. src/app/components/document-detail/document-detail.component.ts - 1033 + 1036 Error executing operation src/app/components/document-detail/document-detail.component.ts - 1044 + 1047 Error downloading document src/app/components/document-detail/document-detail.component.ts - 1093 + 1096 Page Fit src/app/components/document-detail/document-detail.component.ts - 1170 + 1173 PDF edit operation for "" will begin in the background. src/app/components/document-detail/document-detail.component.ts - 1408 + 1411 Error executing PDF edit operation src/app/components/document-detail/document-detail.component.ts - 1420 + 1423 Print failed. src/app/components/document-detail/document-detail.component.ts - 1452 + 1455 Error loading document for printing. src/app/components/document-detail/document-detail.component.ts - 1460 + 1463 An error occurred loading tiff: src/app/components/document-detail/document-detail.component.ts - 1525 + 1528 src/app/components/document-detail/document-detail.component.ts - 1529 + 1532 diff --git a/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts b/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts index 8158be7b2..1a085150e 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts +++ b/src-ui/src/app/components/admin/tasks/tasks.component.spec.ts @@ -16,6 +16,7 @@ import { NgbNavItem, } from '@ng-bootstrap/ng-bootstrap' import { allIcons, NgxBootstrapIconsModule } from 'ngx-bootstrap-icons' +import { throwError } from 'rxjs' import { routes } from 'src/app/app-routing.module' import { PaperlessTask, @@ -28,6 +29,7 @@ import { PermissionsGuard } from 'src/app/guards/permissions.guard' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { PermissionsService } from 'src/app/services/permissions.service' import { TasksService } from 'src/app/services/tasks.service' +import { ToastService } from 'src/app/services/toast.service' import { environment } from 'src/environments/environment' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component' @@ -123,6 +125,7 @@ describe('TasksComponent', () => { let router: Router let httpTestingController: HttpTestingController let reloadSpy + let toastService: ToastService beforeEach(async () => { TestBed.configureTestingModule({ @@ -157,6 +160,7 @@ describe('TasksComponent', () => { httpTestingController = TestBed.inject(HttpTestingController) modalService = TestBed.inject(NgbModal) router = TestBed.inject(Router) + toastService = TestBed.inject(ToastService) fixture = TestBed.createComponent(TasksComponent) component = fixture.componentInstance jest.useFakeTimers() @@ -249,6 +253,42 @@ describe('TasksComponent', () => { expect(dismissSpy).toHaveBeenCalledWith(selected) }) + it('should show an error and re-enable modal buttons when dismissing multiple tasks fails', () => { + component.selectedTasks = new Set([tasks[0].id, tasks[1].id]) + const error = new Error('dismiss failed') + const toastSpy = jest.spyOn(toastService, 'showError') + const dismissSpy = jest + .spyOn(tasksService, 'dismissTasks') + .mockReturnValue(throwError(() => error)) + + let modal: NgbModalRef + modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) + + component.dismissTasks() + expect(modal).not.toBeUndefined() + + modal.componentInstance.confirmClicked.emit() + + expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id, tasks[1].id])) + expect(toastSpy).toHaveBeenCalledWith('Error dismissing tasks', error) + expect(modal.componentInstance.buttonsEnabled).toBe(true) + expect(component.selectedTasks.size).toBe(0) + }) + + it('should show an error when dismissing a single task fails', () => { + const error = new Error('dismiss failed') + const toastSpy = jest.spyOn(toastService, 'showError') + const dismissSpy = jest + .spyOn(tasksService, 'dismissTasks') + .mockReturnValue(throwError(() => error)) + + component.dismissTask(tasks[0]) + + expect(dismissSpy).toHaveBeenCalledWith(new Set([tasks[0].id])) + expect(toastSpy).toHaveBeenCalledWith('Error dismissing task', error) + expect(component.selectedTasks.size).toBe(0) + }) + it('should support dismiss all tasks', () => { let modal: NgbModalRef modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1])) diff --git a/src-ui/src/app/components/admin/tasks/tasks.component.ts b/src-ui/src/app/components/admin/tasks/tasks.component.ts index eb7263137..6f144c58c 100644 --- a/src-ui/src/app/components/admin/tasks/tasks.component.ts +++ b/src-ui/src/app/components/admin/tasks/tasks.component.ts @@ -24,6 +24,7 @@ import { PaperlessTask } from 'src/app/data/paperless-task' import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive' import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe' import { TasksService } from 'src/app/services/tasks.service' +import { ToastService } from 'src/app/services/toast.service' import { ConfirmDialogComponent } from '../../common/confirm-dialog/confirm-dialog.component' import { PageHeaderComponent } from '../../common/page-header/page-header.component' import { LoadingComponentWithPermissions } from '../../loading-component/loading.component' @@ -72,6 +73,7 @@ export class TasksComponent tasksService = inject(TasksService) private modalService = inject(NgbModal) private readonly router = inject(Router) + private readonly toastService = inject(ToastService) public activeTab: TaskTab public selectedTasks: Set = new Set() @@ -154,11 +156,19 @@ export class TasksComponent modal.componentInstance.confirmClicked.pipe(first()).subscribe(() => { modal.componentInstance.buttonsEnabled = false modal.close() - this.tasksService.dismissTasks(tasks) + this.tasksService.dismissTasks(tasks).subscribe({ + error: (e) => { + this.toastService.showError($localize`Error dismissing tasks`, e) + modal.componentInstance.buttonsEnabled = true + }, + }) this.clearSelection() }) } else { - this.tasksService.dismissTasks(tasks) + this.tasksService.dismissTasks(tasks).subscribe({ + error: (e) => + this.toastService.showError($localize`Error dismissing task`, e), + }) this.clearSelection() } } diff --git a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss index 2f9e2c45e..f38bb4002 100644 --- a/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss +++ b/src-ui/src/app/components/common/custom-fields-query-dropdown/custom-fields-query-dropdown.component.scss @@ -41,9 +41,3 @@ min-width: 140px; } } - -.btn-group-xs { - > .btn { - border-radius: 0.15rem; - } -} diff --git a/src-ui/src/app/components/common/input/color/color.component.html b/src-ui/src/app/components/common/input/color/color.component.html index abc4e69eb..6531c70ad 100644 --- a/src-ui/src/app/components/common/input/color/color.component.html +++ b/src-ui/src/app/components/common/input/color/color.component.html @@ -1,19 +1,18 @@
@if (title) { - + }
-     +
-
- +