Compare commits

..

5 Commits

Author SHA1 Message Date
shamoon
0e0419d010 Enhancement: websocket status 2025-09-04 12:43:22 -07:00
shamoon
b2703b4605 Fix: revert pathlib change to database name (#10774) 2025-09-04 06:58:13 -07:00
shamoon
852eb0ef36 Fix chore label assignment logic in PR bot workflow 2025-09-03 17:08:20 -07:00
shamoon
0870d42eae Chore(deps-dev): Bump the frontend-jest-dependencies group in /src-ui with 2 updates (#10770) 2025-09-03 17:06:50 -07:00
dependabot[bot]
e2cf95f8af Chore(deps-dev): Bump the frontend-eslint-dependencies group (#10745)
Bumps the frontend-eslint-dependencies group in /src-ui with 4 updates: [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin), [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser), [@typescript-eslint/utils](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/utils) and [eslint](https://github.com/eslint/eslint).


Updates `@typescript-eslint/eslint-plugin` from 8.38.0 to 8.41.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.41.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.38.0 to 8.41.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.41.0/packages/parser)

Updates `@typescript-eslint/utils` from 8.38.0 to 8.41.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/utils/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.41.0/packages/utils)

Updates `eslint` from 9.32.0 to 9.34.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v9.32.0...v9.34.0)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.41.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.41.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: "@typescript-eslint/utils"
  dependency-version: 8.41.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
- dependency-name: eslint
  dependency-version: 9.34.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: frontend-eslint-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: shamoon <4887959+shamoon@users.noreply.github.com>
2025-09-03 16:53:48 -07:00
11 changed files with 535 additions and 489 deletions

View File

@@ -37,7 +37,7 @@ jobs:
labels.push('bug');
} else if (/^feature/i.test(title)) {
labels.push('enhancement');
} else if (!/^(dependabot)/i.test(title) && /^(chore)/i.test(title)) {
} else if (!/^(dependabot)/i.test(title) && !/^(chore)/i.test(title)) {
labels.push('enhancement'); // Default fallback
}

View File

@@ -1,135 +0,0 @@
name: Paperless-ngx Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g., 2.18.3)"
required: true
type: string
permissions:
contents: write
actions: read
concurrency:
group: release-main
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-24.04
steps:
- name: Checkout (full)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git
run: |
git config user.name "${{ github.actor }}"
git config user.email "${{ github.actor }}@users.noreply.github.com"
- name: Sanitize & validate input
id: ver
shell: bash
run: |
RAW="${{ github.event.inputs.version }}"
# trim spaces + strip leading 'v' if present
RAW="${RAW//[[:space:]]/}"
RAW="${RAW#v}"
# basic semver X.Y.Z
if [[ ! "$RAW" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "❌ Invalid version: '$RAW' (expected X.Y.Z or vX.Y.Z)"; exit 1
fi
MAJOR="${RAW%%.*}"
REST="${RAW#*.}"
MINOR="${REST%%.*}"
PATCH="${REST#*.}"
echo "version=$RAW" >> "$GITHUB_OUTPUT"
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
echo "patch=$PATCH" >> "$GITHUB_OUTPUT"
echo "✅ Using version $RAW"
- name: Ensure tag does not already exist
run: |
git fetch --tags
if git rev-parse "v${{ steps.ver.outputs.version }}" >/dev/null 2>&1; then
echo "❌ Tag v${{ steps.ver.outputs.version }} already exists"; exit 1
fi
- name: Update local branches
run: |
git fetch origin main dev
- name: Fast-forward main to dev (no merge commits)
run: |
# Reset local main to remote, then try fast-forward to dev.
git checkout main
git reset --hard origin/main
# --ff-only ensures the workflow fails if the branches diverged.
git merge --ff-only origin/dev
echo "✅ main fast-forwarded to dev at $(git rev-parse --short HEAD)"
- name: Bump versions in files
shell: bash
run: |
VER="${{ steps.ver.outputs.version }}"
MAJ="${{ steps.ver.outputs.major }}"
MIN="${{ steps.ver.outputs.minor }}"
PAT="${{ steps.ver.outputs.patch }}"
# 1) pyproject.toml: [project] version = "X.Y.Z"
sed -i -E 's/^version = "[0-9]+\.[0-9]+\.[0-9]+"/version = "'"$VER"'"/' pyproject.toml
# 2) src-ui/package.json: "version": "X.Y.Z"
# Use jq if available; otherwise sed fallback.
if command -v jq >/dev/null 2>&1; then
tmp=$(mktemp)
jq --arg v "$VER" '.version=$v' src-ui/package.json > "$tmp" && mv "$tmp" src-ui/package.json
else
sed -i -E 's/"version": "[0-9]+\.[0-9]+\.[0-9]+"/"version": "'"$VER"'"/' src-ui/package.json
fi
# 3) src-ui/src/environments/environment.prod.ts: version: 'X.Y.Z'
sed -i -E "s/version: '[0-9]+\.[0-9]+\.[0-9]+'/version: '$VER'/" src-ui/src/environments/environment.prod.ts
# 4) src/paperless/version.py: __version__ = (X, Y, Z)
sed -i -E "s/__version__:\s*Final\[tuple\[int,\s*int,\s*int\]\]\s*=\s*\([0-9]+,\s*[0-9]+,\s*[0-9]+\)/__version__: Final[tuple[int, int, int]] = ($MAJ, $MIN, $PAT)/" src/paperless/version.py
# 5) uv.lock: in the [[package]] name = "paperless-ngx" block, set version = "X.Y.Z"
# This awk edits only the block for paperless-ngx.
awk -v ver="$VER" '
BEGIN{inpkg=0}
/^\[\[package\]\]/{inpkg=0}
/^\[\[package\]\]/{print; next}
{print > "/dev/stdout"}
' uv.lock >/dev/null 2>&1 # noop to ensure awk exists
# More robust in-place edit with awk:
awk -v ver="$VER" '
BEGIN{inpkg=0}
/^\[\[package\]\]/{inpkg=0; print; next}
/^name = "paperless-ngx"/{inpkg=1; print; next}
inpkg && /^version = "/{
sub(/version = "[0-9]+\.[0-9]+\.[0-9]+"/, "version = \"" ver "\"")
print; next
}
{print}
' uv.lock > uv.lock.new && mv uv.lock.new uv.lock
echo "✅ Files updated to $VER"
- name: Commit bump (if changes)
run: |
if git diff --quiet; then
echo " No changes to commit (versions may already match)";
else
git add pyproject.toml src-ui/package.json src-ui/src/environments/environment.prod.ts src/paperless/version.py uv.lock
git commit -m "Bump version to ${{ steps.ver.outputs.version }}"
fi
- name: Push main
run: |
# Push branch (even if no commit, ensures remote main == local)
git push origin HEAD:main
- name: Create and push tag
run: |
VER="${{ steps.ver.outputs.version }}"
git tag -a "v${VER}" -m "Release v${VER}"
git push origin "v${VER}"
- name: Done
run: echo "🎉 Release v${{ steps.ver.outputs.version }} created and pushed."

View File

@@ -56,12 +56,12 @@
"@playwright/test": "^1.55.0",
"@types/jest": "^30.0.0",
"@types/node": "^24.3.0",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"@typescript-eslint/utils": "^8.38.0",
"eslint": "^9.32.0",
"jest": "30.0.5",
"jest-environment-jsdom": "^30.0.5",
"@typescript-eslint/eslint-plugin": "^8.41.0",
"@typescript-eslint/parser": "^8.41.0",
"@typescript-eslint/utils": "^8.41.0",
"eslint": "^9.34.0",
"jest": "30.1.3",
"jest-environment-jsdom": "^30.1.2",
"jest-junit": "^16.0.0",
"jest-preset-angular": "^15.0.0",
"jest-websocket-mock": "^2.5.0",

787
src-ui/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -185,7 +185,8 @@ export class SettingsComponent
this.systemStatus.tasks.classifier_status ===
SystemStatusItemStatus.ERROR ||
this.systemStatus.tasks.sanity_check_status ===
SystemStatusItemStatus.ERROR
SystemStatusItemStatus.ERROR ||
this.systemStatus.websocket_connected === SystemStatusItemStatus.ERROR
)
}

View File

@@ -254,6 +254,18 @@
<h6><ng-container i18n>Error</ng-container>:</h6> <span class="font-monospace small">{{status.tasks.sanity_check_error}}</span>
}
</ng-template>
<dt i18n>WebSocket Connection</dt>
<dd>
<span class="btn btn-sm pe-none align-items-center btn-dark text-uppercase small">
@if (status.websocket_connected === 'OK') {
<ng-container i18n>OK</ng-container>
<i-bs name="check-circle-fill" class="text-primary ms-2 lh-1"></i-bs>
} @else {
<ng-container i18n>Error</ng-container>
<i-bs name="exclamation-triangle-fill" class="text-danger ms-2 lh-1"></i-bs>
}
</span>
</dd>
</dl>
</div>
</div>

View File

@@ -24,7 +24,7 @@ import {
} from '@angular/core/testing'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { of, throwError } from 'rxjs'
import { Subject, of, throwError } from 'rxjs'
import { PaperlessTaskName } from 'src/app/data/paperless-task'
import {
InstallType,
@@ -34,6 +34,7 @@ import {
import { SystemStatusService } from 'src/app/services/system-status.service'
import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { SystemStatusDialogComponent } from './system-status-dialog.component'
const status: SystemStatus = {
@@ -77,6 +78,8 @@ describe('SystemStatusDialogComponent', () => {
let tasksService: TasksService
let systemStatusService: SystemStatusService
let toastService: ToastService
let websocketStatusService: WebsocketStatusService
let websocketSubject: Subject<boolean> = new Subject<boolean>()
beforeEach(async () => {
await TestBed.configureTestingModule({
@@ -98,6 +101,12 @@ describe('SystemStatusDialogComponent', () => {
tasksService = TestBed.inject(TasksService)
systemStatusService = TestBed.inject(SystemStatusService)
toastService = TestBed.inject(ToastService)
websocketStatusService = TestBed.inject(WebsocketStatusService)
jest
.spyOn(websocketStatusService, 'onConnectionStatus')
.mockImplementation(() => {
return websocketSubject.asObservable()
})
fixture.detectChanges()
})
@@ -168,4 +177,19 @@ describe('SystemStatusDialogComponent', () => {
component.ngOnInit()
expect(component.versionMismatch).toBeFalsy()
})
it('should update websocket connection status', () => {
websocketSubject.next(true)
expect(component.status.websocket_connected).toEqual(
SystemStatusItemStatus.OK
)
websocketSubject.next(false)
expect(component.status.websocket_connected).toEqual(
SystemStatusItemStatus.ERROR
)
websocketSubject.next(true)
expect(component.status.websocket_connected).toEqual(
SystemStatusItemStatus.OK
)
})
})

View File

@@ -1,5 +1,5 @@
import { Clipboard, ClipboardModule } from '@angular/cdk/clipboard'
import { Component, OnInit, inject } from '@angular/core'
import { Component, OnDestroy, OnInit, inject } from '@angular/core'
import {
NgbActiveModal,
NgbModalModule,
@@ -7,6 +7,7 @@ import {
NgbProgressbarModule,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule } from 'ngx-bootstrap-icons'
import { Subject, takeUntil } from 'rxjs'
import { PaperlessTaskName } from 'src/app/data/paperless-task'
import {
SystemStatus,
@@ -18,6 +19,7 @@ import { PermissionsService } from 'src/app/services/permissions.service'
import { SystemStatusService } from 'src/app/services/system-status.service'
import { TasksService } from 'src/app/services/tasks.service'
import { ToastService } from 'src/app/services/toast.service'
import { WebsocketStatusService } from 'src/app/services/websocket-status.service'
import { environment } from 'src/environments/environment'
@Component({
@@ -34,13 +36,14 @@ import { environment } from 'src/environments/environment'
NgxBootstrapIconsModule,
],
})
export class SystemStatusDialogComponent implements OnInit {
export class SystemStatusDialogComponent implements OnInit, OnDestroy {
activeModal = inject(NgbActiveModal)
private clipboard = inject(Clipboard)
private systemStatusService = inject(SystemStatusService)
private tasksService = inject(TasksService)
private toastService = inject(ToastService)
private permissionsService = inject(PermissionsService)
private websocketStatusService = inject(WebsocketStatusService)
public SystemStatusItemStatus = SystemStatusItemStatus
public PaperlessTaskName = PaperlessTaskName
@@ -51,6 +54,7 @@ export class SystemStatusDialogComponent implements OnInit {
public copied: boolean = false
private runningTasks: Set<PaperlessTaskName> = new Set()
private unsubscribeNotifier: Subject<any> = new Subject()
get currentUserIsSuperUser(): boolean {
return this.permissionsService.isSuperUser()
@@ -65,6 +69,17 @@ export class SystemStatusDialogComponent implements OnInit {
if (this.versionMismatch) {
this.status.pngx_version = `${this.status.pngx_version} (frontend: ${this.frontendVersion})`
}
this.status.websocket_connected = this.websocketStatusService.isConnected()
? SystemStatusItemStatus.OK
: SystemStatusItemStatus.ERROR
this.websocketStatusService
.onConnectionStatus()
.pipe(takeUntil(this.unsubscribeNotifier))
.subscribe((connected) => {
this.status.websocket_connected = connected
? SystemStatusItemStatus.OK
: SystemStatusItemStatus.ERROR
})
}
public close() {
@@ -97,7 +112,7 @@ export class SystemStatusDialogComponent implements OnInit {
this.runningTasks.delete(taskName)
this.systemStatusService.get().subscribe({
next: (status) => {
this.status = status
Object.assign(this.status, status)
},
})
},
@@ -110,4 +125,9 @@ export class SystemStatusDialogComponent implements OnInit {
},
})
}
ngOnDestroy(): void {
this.unsubscribeNotifier.next(this)
this.unsubscribeNotifier.complete()
}
}

View File

@@ -44,4 +44,5 @@ export interface SystemStatus {
sanity_check_last_run: string // ISO date string
sanity_check_error: string
}
websocket_connected?: SystemStatusItemStatus // added client-side
}

View File

@@ -103,6 +103,7 @@ export class WebsocketStatusService {
private documentConsumptionFinishedSubject = new Subject<FileStatus>()
private documentConsumptionFailedSubject = new Subject<FileStatus>()
private documentDeletedSubject = new Subject<boolean>()
private connectionStatusSubject = new Subject<boolean>()
private get(taskId: string, filename?: string) {
let status =
@@ -153,6 +154,15 @@ export class WebsocketStatusService {
this.statusWebSocket = new WebSocket(
`${environment.webSocketProtocol}//${environment.webSocketHost}${environment.webSocketBaseUrl}status/`
)
this.statusWebSocket.onopen = () => {
this.connectionStatusSubject.next(true)
}
this.statusWebSocket.onclose = () => {
this.connectionStatusSubject.next(false)
}
this.statusWebSocket.onerror = () => {
this.connectionStatusSubject.next(false)
}
this.statusWebSocket.onmessage = (ev: MessageEvent) => {
const {
type,
@@ -286,4 +296,12 @@ export class WebsocketStatusService {
onDocumentDeleted() {
return this.documentDeletedSubject
}
onConnectionStatus() {
return this.connectionStatusSubject.asObservable()
}
isConnected(): boolean {
return this.statusWebSocket?.readyState === WebSocket.OPEN
}
}

View File

@@ -2764,7 +2764,7 @@ class SystemStatusView(PassUserMixin):
install_type = "docker"
db_conn = connections["default"]
db_url = db_conn.settings_dict["NAME"]
db_url = str(db_conn.settings_dict["NAME"])
db_error = None
try: